<?xml version="1.0" encoding="utf-8"?>
  <feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://wolfgirl.dev/blog/</id>
    <title>PolyWolf's Blog</title>
    <subtitle>a blog written by PolyWolf</subtitle>
    <icon>/apple-touch-icon.png</icon>
    <link href="https://wolfgirl.dev/blog/rss.xml" rel="self" />
    <link href="https://wolfgirl.dev/blog/" />
    <updated>2026-08-02T16:17:41Z</updated>
    
    <entry>
      <title><![CDATA[Yotsuba&!: What Camera Does 虎子 (Torako) Use?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-08-02-yotsuba-what-camera-does-torako-use-/" />
      <id>https://wolfgirl.dev/blog/2026-08-02-yotsuba-what-camera-does-torako-use-/</id>
      <published>2026-08-02T16:17:41Z</published>
      <updated>2026-08-02T16:17:41Z</updated>
      <summary><![CDATA[I will do one of these for every manga camera I see, this is a promise.]]></summary>
      <content type="html"><![CDATA[<p>The first time we see it, the badge is partially obscured, but just from the boot shape we can be pretty sure it's a Nikon:</p><p><img alt="Torako holds her camera and Yotsuba takes a closer look" src=https://static.wolfgirl.dev/polywolf/blog/019fc32c-6dbd-7bb0-a050-31a9d379f4a8/IMG_4001.jpeg> and maybe some other incidental details too...</p><details><summary>Translation</summary> Torako: Well... Lately I've been studying photography so... Koiwai (Yotsuba's dad): Ah, cool! Well I don't mind if you take photos so long as you give us prints later.</details><p>We can almost make out the badge just in that one shot, but later shots make it much more explicit:</p><p><img alt="Torako holds up her camera to get a picture, and Yotsuba smiles with unnervingly large eyes" src=https://static.wolfgirl.dev/polywolf/blog/019fc32c-6dbd-7bb0-a050-31a9d379f4a8/IMG_4009.jpeg> <img alt="Yotsuba waves to the camera while riding her bike, and in 3 separate frames, is in the process of falling with her bike down a hill. Torako & Asagi look on in mild shock, failing to react in time" src=https://static.wolfgirl.dev/polywolf/blog/019fc32c-6dbd-7bb0-a050-31a9d379f4a8/IMG_4011.jpeg></p><p>It's a <a href=https://en.wikipedia.org/wiki/Nikon_F100><strong>Nikon F100</strong></a>, a classic 35mm SLR film camera. Mystery solved that was easy!!</p><p>If I were really serious about things, I would try to find out all their models of bikes, but I am a lowly camera geek so I'll leave that for others.</p><p><img alt="Torako, Yotsuba, & Asagi on their bikes, ready to go. Torako's is a folding bike, Yotsuba's is a children's bike, and Asagi's is a cruiser." src=https://static.wolfgirl.dev/polywolf/blog/019fc32c-6dbd-7bb0-a050-31a9d379f4a8/IMG_4002.jpeg></p><p>You should read <em>Yotsuba&!</em> btw. It is very good and I have been learning a lot by struggling thru the Japanese originals. At the start I was doing maybe 2 pages every 30min and now I am up to 20 pages in that time.</p><hr><p>But enough about that. Really, this post is a low-effort one intended to answer "why have I not meaningfully posted for over a month". In order of relevance:</p><ol><li>I've acquired a medical condition that is forcing me off estrogen for at least another 2 months, which sucks major ass and has been an acute reminder of why I went on in the first place.</li><li>I have a few drafts in various states of completion, but I haven't had enough time/energy due to my new job. It is a fine job but unemployment will always be better for blogging turns out.</li><li>Other hobbies like "learning FL studio" & "hanging out with friends" have eaten into the time I would have otherwise used to blog.</li></ol><p>There likely isn't anyone I am disappointing here except myself, but to everyone who's subscribed to this over RSS, thank you for reading.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Blogroll & Button Wall & Small Redesign]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-06-30-blogroll-button-wall-small-redesign/" />
      <id>https://wolfgirl.dev/blog/2026-06-30-blogroll-button-wall-small-redesign/</id>
      <published>2026-07-01T00:21:43Z</published>
      <updated>2026-07-01T00:21:43Z</updated>
      <summary><![CDATA[informative post to mark a couple milestones]]></summary>
      <content type="html"><![CDATA[<p>I've been hacking away on my website here 'n there, and just wanted to highlight a few changes I'm proud of :)</p><p>It's mostly been small things:</p><ul><li>no monospace font on main pages (just art page now), everywhere looks more consistent</li><li>header links so you can link to a specific section of a blog post</li><li>minor styling change: superscripts don't cause big line gaps</li></ul><p>But there's a few bigger ones too:</p><ul><li><a href=/blogroll/>blogroll</a></li><li><a href=/buttonwall/>button wall</a></li><li>by-year <a href=/blog/archives/>archives</a></li></ul><p>Of these, I am quite proud of the buttons I made for the button wall. However, it currently doesn't have anyone else's buttons. If you have your own 88x31 button, please message me! I'd love to expand my social web even further :3</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Async Task Locals From Scratch]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-06-16-async-task-locals-from-scratch/" />
      <id>https://wolfgirl.dev/blog/2026-06-16-async-task-locals-from-scratch/</id>
      <published>2026-06-16T13:09:40Z</published>
      <updated>2026-06-16T13:09:40Z</updated>
      <summary><![CDATA[Some literate programming on a small crate I've written]]></summary>
      <content type="html"><![CDATA[<p>To make a long story short, let's assume we have the following constraints, ignoring where exactly these constraints come from:</p><blockquote><ul><li>We want to associate some data with an async task in Rust, such that, while that task is being polled, we can retrieve said data deeper in the callstack, without having to manually pass the data down thru function arguments.</li><li>We don't want to use the <code>tokio</code> ecosystem.</li></ul></blockquote><p>That is, we want to do what <a href=https://docs.rs/tokio/latest/tokio/macro.task_local.html><code>tokio::task_local!</code></a> does, but don't want to use tokio. What do? Well... I couldn't find anything else that does it, so I did it myself (:</p><h2><a aria-hidden=true class=anchor href=#buckle-up-its-apple-pie-baking-time id=heading-buckle-up-its-apple-pie-baking-time></a>Buckle Up It's <a href=https://www.goodreads.com/quotes/32952-if-you-wish-to-make-an-apple-pie-from-scratch>Apple-Pie-Baking Time</a></h2><p>To understand what we want, we must first understand why what the standard library gives is not enough. To understand what the standard library gives, we must first understand the execution model upon which it relies. I did say "from scratch"<sup class=footnote-ref><a data-footnote-ref href=#fn-other-reading id=fnref-other-reading>1</a></sup>, after all :P</p><p>Most operating systems organize code execution into two distinct units: <strong>Processes</strong> & <strong>Threads</strong>. Processes roughly delineate "what memory is visible", while Threads roughly delineate "what instructions are running". A single Process may contain one or more Threads, and when there are multiple Threads in a Process, computer scientists call it a "<a href=https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)>Multithreaded Architecture</a>" with "<a href=https://en.wikipedia.org/wiki/Parallel_programming_model#Shared_memory>Shared Memory Parallelism</a>": there are literally multiple Threads, sharing memory in the same Process.</p><p>This Process/Thread model requires a bit of work on the programmer's side to make sure Threads don't step on each other's toes while they're running. Most normal code assumes it's the only one with access to the memory it touches, and will get very upset if some other Thread scribbles over its work while it's not looking. If Threads <em>don't</em> touch each other's memory, or there is some mechanism by which they co-ordinate their mutual touching<sup class=footnote-ref><a data-footnote-ref href=#fn-gay id=fnref-gay>2</a></sup>, that property is called "Thread-Safety".</p><p>Naturally, thread-safety is a very important property (ensuring it at compile time is a big motivator for Rust!), so we'll need to maintain it while achieving our overall goal. There are various strategies by which we can ensure thread-safety, but by far the simplest is "just use variables on the stack lol". Each Thread's stack is unique by construction<sup class=footnote-ref><a data-footnote-ref href=#fn-thread-stacks id=fnref-thread-stacks>3</a></sup>, so if we create a value on the stack + pass a reference down thru arguments to anywhere that needs it, boom done ezpz.</p><p>But remember, we have a special requirement:</p><blockquote><p>[...] can retrieve said data deeper in the callstack, <strong>without having to manually pass the data down thru arguments</strong>.</p></blockquote><p>So really, we want something that works more like a "global" variable, but without the complications that come from multiple Threads sharing a single global. Fortunately, this already exists! <a href=https://en.wikipedia.org/wiki/Thread-local_storage><strong>Thread-Local Storage</strong></a>, aka TLS, creates variables that are "local" in the sense they can only be accessed by a single thread, while still being "global" in the sense that any function can touch them. Different platforms can have greatly varying implementations of TLS, all we need to know is: each thread has its own place to put its global variables, no more toe-stepping yay everyone is happy. C/C++ uses the <a href=https://en.cppreference.com/w/c/thread/thread_local.html><code>thread_local</code> keyword</a> for this, while Rust uses a <a href=https://doc.rust-lang.org/std/macro.thread_local.html><code>std::thread_local!</code> macro</a>.</p><h2><a aria-hidden=true class=anchor href=#are-thread-locals-sufficient id=heading-are-thread-locals-sufficient></a>Are Thread-Locals Sufficient?</h2><p>Turns out no! There's a bit more universe we need to conjure before we can start on our pie, specifically the difference between an Operating System Thread and a Rust Task.</p><p>With Threads, the OS automatically manages things like "what Thread is running on this CPU core?", "what Threads are available to run but aren't?", & "ok time to switch to different Thread". However, switching between Threads at the OS level is quite a slow operation<sup class=footnote-ref><a data-footnote-ref href=#fn-os-slow id=fnref-os-slow>4</a></sup> compared to normal code. Instead, languages will often make their own thread-like things in "userspace" (that is, without any help from the OS) to help avoid this slowdow while keeping the other benefits of concurrent code. These thread-like things are called either <a href=https://en.wikipedia.org/wiki/Green_thread>Green Threads</a> or <a href=https://en.wikipedia.org/wiki/Coroutine>Coroutines</a>, depending on certain factors.</p><details><summary>Aside on those factors</summary> <p>The main factor is "can it run on its own" (Green Thread, aka pre-emptive multi-tasking) or "does it need something else to drive it" (Coroutine, aka co-operative multi-tasking). Common examples of Green Threads are <a href=https://en.wikipedia.org/wiki/Erlang_(programming_language)>Erlang</a>'s processes & Go's <a href=https://go.dev/tour/concurrency/1>goroutines</a> (yeah they really messed up the name there), while examples of Coroutines include Python's <a href=https://docs.python.org/3/library/asyncio-task.html>Coroutines</a>, C++'s <a href=https://en.cppreference.com/w/cpp/language/coroutines.html>Coroutines</a>, & Rust's <a href=https://doc.rust-lang.org/std/future/trait.Future.html><code>Future</code></a> & <a href=https://doc.rust-lang.org/std/iter/trait.Iterator.html><code>Iterator</code></a> traits. Javascript's <a href=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise><code>Promise</code></a> objects are interesting, because at first glance they seem to be a Coroutine, what with the <code>async</code>/<code>await</code> syntax, but actually they're Green Threads because they start running all on their own, and the <code>await</code> syntax is just a fancy way of chaining callbacks together.</p> <p>Anyways!! These distinctions tend to be pretty controversial, and I'm sure I'll get some helpful comments clarifying how I got some of this wrong (:</p></details><p>For now, I'll just group them all together under an umbrella labeled "Tasks", upsetting the largest number of people possible :)</p><p>Anyways! The important thing to notice is, <strong>Tasks are not Threads</strong>, so we can't <em>only</em> use Thread-Local Storage, because:</p><ol><li>We could have multiple Tasks running on the same Thread, OR</li><li>We could have a Task that gets run on multiple Threads at different points in its execution (for throughput reasons).</li></ol><p>Specifically, a plan to "set a thread-local when a Task starts running, and unset it when the Task finishes running" doesn't work, because:</p><ol><li>Other Tasks that start running on the same Thread might share that same thread-local, OR</li><li>The Task might get shuffled to another Thread between it setting a thread-local and it trying to read from the thread-local.</li></ol><p>both of which are failure modes that will make us sad :(</p><p>So! With all that out of the way, I hope I've convinced you we really do need a special construct for an Task-Local. Now to build it...</p><h2><a aria-hidden=true class=anchor href=#whats-our-strategy-capn id=heading-whats-our-strategy-capn></a>What's Our Strategy Cap'n?</h2><p>How Rust's Tasks work is, <em>they only ever do work when their <a href=https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll><code>Future::poll()</code></a> method is called</em><sup class=footnote-ref><a data-footnote-ref href=#fn-coroutine-green-thread id=fnref-coroutine-green-thread>5</a></sup>. That, finally, leads to our key insight: in order to have a Task-Local value, we simply need to set some global value <em>only while <code>poll()</code> is being called</em>. Using a Thread-Local value as that global works because, for the duration of that single <code>poll()</code> call, <em>we can't change threads</em>. So we're free to save/restore on that single thread as we please:</p><details><summary>Aside on upcoming use of <code>unsafe</code> blocks</summary> <p>You should ignore them. They are for dealing with <a href=https://doc.rust-lang.org/std/pin/index.html><code>Pin</code></a> semantics that, in a real codebase, you should use the <a href=https://docs.rs/pin-project/latest/pin_project/><code>pin_project</code></a> crate for, which handles it in a safe way. I only show code without that crate to demonstrate there's nothing magic going on, just stdlib functions.</p></details><pre class=syntax-highlighting><code class=language-rust><span class=keyword>use</span> std<span class=punctuation>::</span>cell<span class=punctuation>::</span><span class=constructor>RefCell</span><span class=punctuation>;</span>
<span class=keyword>use</span> std<span class=punctuation>::</span>pin<span class=punctuation>::</span><span class=constructor>Pin</span><span class=punctuation>;</span>
<span class=keyword>use</span> std<span class=punctuation>::</span>task<span class=punctuation>::{</span><span class=constructor>Context</span><span class=punctuation>,</span> <span class=constructor>Poll</span><span class=punctuation>};</span>
<span class=keyword>use</span> std<span class=punctuation>::</span>thread<span class=punctuation>::</span><span class=constructor>LocalKey</span><span class=punctuation>;</span>

<span class=comment>/// Container type that implements [`std::future::Future`] by wrapping an existing one, as well as saving/restoring a task-local made available to it.
</span><span class=keyword>pub</span> <span class=keyword>struct</span> <span class=type>Scoped</span><span class=punctuation>&lt;</span><span class=type>F</span><span class=punctuation>:</span> <span class=type>Future</span><span class=punctuation>,</span> <span class=type>T</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=comment>/// Thread-Local Storage that holds the current task-local value.
</span>    <span class=property>tls</span><span class=punctuation>:</span> <span class=operator>&'</span><span class=label>static</span> <span class=type>LocalKey</span><span class=punctuation>&lt;</span><span class=type>RefCell</span><span class=punctuation>&lt;</span><span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>>>,</span>
    <span class=comment>/// When the future IS NOT being polled: the value we want to store is inside `curr`.
</span>    <span class=comment>/// When the future IS being polled: the previous value stored inside `curr`, if we are running inside another [`Scoped`].
</span>    <span class=property>curr</span><span class=punctuation>:</span> <span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
    <span class=comment>/// The future we're wrapping.
</span>    <span class=property>fut</span><span class=punctuation>:</span> <span class=type>F</span><span class=punctuation>,</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>F</span><span class=punctuation>:</span> <span class=type>Future</span><span class=punctuation>,</span> <span class=type>T</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>></span> <span class=type>Future</span> <span class=keyword>for</span> <span class=type>Scoped</span><span class=punctuation>&lt;</span><span class=type>F</span><span class=punctuation>,</span> <span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Output</span> = <span class=type>F</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>poll</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>:</span> <span class=type>Pin</span><span class=punctuation>&lt;</span><span class=operator>&</span><span class=keyword>mut</span> <span class=type>Self</span><span class=punctuation>>,</span> <span class=variable>cx</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=type>Context</span><span class=punctuation>&lt;</span><span class=operator>'</span><span class=label>_</span><span class=punctuation>>)</span> -> <span class=type>Poll</span><span class=punctuation>&lt;</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=comment>// SAFETY: `this` never moves out its value, so `self` stays pinned.</span>
        <span class=keyword>let</span> this = <span class=keyword>unsafe</span> <span class=punctuation>{</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=variable>self</span><span class=punctuation>.</span><span class=function>get_unchecked_mut</span><span class=punctuation>()</span> <span class=punctuation>};</span>
        <span class=comment>// SAFETY: because `self` is still pinned, so is `fut`.</span>
        <span class=keyword>let</span> fut = <span class=keyword>unsafe</span> <span class=punctuation>{</span> <span class=type>Pin</span><span class=punctuation>::</span><span class=function>new_unchecked</span><span class=punctuation>(</span><span class=operator>&</span><span class=keyword>mut</span> this<span class=punctuation>.</span><span class=property>fut</span><span class=punctuation>)</span> <span class=punctuation>};</span>
        <span class=comment>// The swap only ever moves values _stored inside_ `self`;</span>
        <span class=comment>// it doesn't change the location of `self` directly.</span>
        <span class=keyword>let</span> curr = <span class=operator>&</span><span class=keyword>mut</span> this<span class=punctuation>.</span><span class=property>curr</span><span class=punctuation>;</span>
        <span class=keyword>let</span> <span class=keyword>mut</span> swap = || this<span class=punctuation>.</span><span class=property>tls</span><span class=punctuation>.</span><span class=function>with_borrow_mut</span><span class=punctuation>(</span>|prev| std<span class=punctuation>::</span>mem<span class=punctuation>::</span><span class=function>swap</span><span class=punctuation>(</span>curr<span class=punctuation>,</span> prev<span class=punctuation>));</span>

        <span class=comment>// Swap in the value from "the stack" (`this.curr`) into "global memory" (`this.tls`) while polling.</span>
        <span class=function>swap</span><span class=punctuation>();</span>
        <span class=keyword>let</span> out = fut<span class=punctuation>.</span><span class=function>poll</span><span class=punctuation>(</span>cx<span class=punctuation>);</span>
        <span class=comment>// Swap the value from "global memory" back onto "the stack" to save it for when we get polled later.</span>
        <span class=function>swap</span><span class=punctuation>();</span>

        out
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>I hope all that exposition helped understand <em>why</em> we're doing what we're doing, that we didn't just pull this implementation out of thin air, why we're putting save/restore logic in <code>poll()</code> instead of in a constructor/<code>Drop</code> impl.</p><p>Now all that's left is to provide some helpers so this wrapper has a more <code>tokio</code>-like API. First, we define a helper struct to provide <code>scope()</code> & <code>with()</code> functions that are very similar to <a href=https://docs.rs/tokio/latest/tokio/task/struct.LocalKey.html#method.scope>what <code>tokio::task::LocalKey</code> has</a>:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>pub</span> <span class=keyword>struct</span> <span class=type>ScopeBuilder</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=property>tls</span><span class=punctuation>:</span> <span class=operator>&'</span><span class=label>static</span> <span class=type>LocalKey</span><span class=punctuation>&lt;</span><span class=type>RefCell</span><span class=punctuation>&lt;</span><span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>>>,</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>></span> <span class=type>ScopeBuilder</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=comment>/// Create a new task wrapper builder for the given Thread-Local Storage
</span>    <span class=keyword>pub</span> <span class=keyword>const</span> <span class=keyword>fn</span> <span class=function>new</span><span class=punctuation>(</span><span class=variable>tls</span><span class=punctuation>:</span> <span class=operator>&'</span><span class=label>static</span> <span class=type>LocalKey</span><span class=punctuation>&lt;</span><span class=type>RefCell</span><span class=punctuation>&lt;</span><span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>>>)</span> -> <span class=type>Self</span> <span class=punctuation>{</span>
        <span class=type>Self</span> <span class=punctuation>{</span> tls <span class=punctuation>}</span>
    <span class=punctuation>}</span>

    <span class=comment>/// Given the Thread-Local Storage provided at builder creation, construct a [`Scoped`] future that will expose the given `value`.
</span>    <span class=keyword>pub</span> <span class=keyword>fn</span> <span class=function>scope</span><span class=punctuation>&lt;</span><span class=type>F</span><span class=punctuation>:</span> <span class=type>Future</span><span class=punctuation>>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>value</span><span class=punctuation>:</span> <span class=type>T</span><span class=punctuation>,</span> <span class=variable>fut</span><span class=punctuation>:</span> <span class=type>F</span><span class=punctuation>)</span> -> <span class=type>Scoped</span><span class=punctuation>&lt;</span><span class=type>F</span><span class=punctuation>,</span> <span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>Scoped</span> <span class=punctuation>{</span>
            <span class=property>tls</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>tls</span><span class=punctuation>,</span>
            <span class=property>curr</span><span class=punctuation>:</span> <span class=function>Some</span><span class=punctuation>(</span>value<span class=punctuation>),</span>
            fut<span class=punctuation>,</span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>

    <span class=comment>/// Read from the Thread-Local Storage, which will be `Some` if we are inside a [`Scoped`] future.
</span>    <span class=comment>/// Takes a callback to ensure the lifetimes work out.
</span>    <span class=keyword>pub</span> <span class=keyword>fn</span> <span class=function>with</span><span class=punctuation>&lt;</span><span class=type>V</span><span class=punctuation>>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnOnce</span><span class=punctuation>(</span><span class=type>Option</span><span class=punctuation>&lt;</span><span class=operator>&</span><span class=type>T</span><span class=punctuation>>)</span> -> <span class=type>V</span><span class=punctuation>)</span> -> <span class=type>V</span> <span class=punctuation>{</span>
        <span class=variable>self</span><span class=punctuation>.</span><span class=property>tls</span><span class=punctuation>.</span><span class=function>with_borrow</span><span class=punctuation>(</span>|value| <span class=function>f</span><span class=punctuation>(</span>value<span class=punctuation>.</span><span class=function>as_ref</span><span class=punctuation>()))</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Then, for completeness's sake, we also define a macro similar to <a href=https://docs.rs/tokio/latest/tokio/macro.task_local.html><code>tokio::task_local!</code></a>, which creates the Thread-Local Storage & associated <code>ScopeBuilder</code> at the same time in an ergonomic fashion:</p><pre class=syntax-highlighting><code class=language-rust><span class=attribute>#</span><span class=punctuation>[</span><span class=attribute>macro_export</span><span class=punctuation>]</span>
<span class=keyword>macro_rules</span>! task_local <span class=punctuation>{</span>
    <span class=punctuation>(</span>$<span class=punctuation>(</span><span class=keyword>static</span> $ident<span class=punctuation>:</span>ident <span class=punctuation>:</span> $ty<span class=punctuation>:</span>ty <span class=punctuation>;)</span><span class=operator>*</span><span class=punctuation>)</span> => <span class=punctuation>{</span>$<span class=punctuation>(</span>
        <span class=keyword>static</span> $ident<span class=punctuation>:</span> $crate<span class=punctuation>::</span><span class=constructor>ScopeBuilder</span>&lt;$ty> = <span class=punctuation>{</span>
            std<span class=punctuation>::</span>thread_local! <span class=punctuation>{</span>
                <span class=keyword>static</span> <span class=constructor>LOCAL</span><span class=punctuation>:</span> std<span class=punctuation>::</span>cell<span class=punctuation>::</span><span class=constructor>RefCell</span><span class=punctuation>&lt;</span><span class=constructor>Option</span><span class=punctuation>&lt;</span>$ty<span class=punctuation>>></span> = <span class=keyword>const</span> <span class=punctuation>{</span> std<span class=punctuation>::</span>cell<span class=punctuation>::</span><span class=type>RefCell</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>(</span><span class=constructor>None</span><span class=punctuation>)</span> <span class=punctuation>};</span>
            <span class=punctuation>}</span>
            $crate<span class=punctuation>::</span><span class=type>ScopeBuilder</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>(</span><span class=operator>&</span><span class=constructor>LOCAL</span><span class=punctuation>)</span>
        <span class=punctuation>};</span>
    <span class=punctuation>)</span><span class=operator>*</span><span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Putting it all together, usage looks something like:</p><pre class=syntax-highlighting><code class=language-rust><span class=macro>task_local!</span> <span class=punctuation>{</span>
    <span class=keyword>static</span> <span class=constructor>SOME_GLOBAL</span><span class=punctuation>:</span> <span class=type>usize</span><span class=punctuation>;</span>
<span class=punctuation>}</span>

<span class=comment>/// Prints the value currently contained in [`SOME_GLOBAL`]
</span><span class=keyword>fn</span> <span class=function>print</span><span class=punctuation>()</span> <span class=punctuation>{</span>
    <span class=keyword>let</span> v = <span class=constructor>SOME_GLOBAL</span><span class=punctuation>.</span><span class=function>with</span><span class=punctuation>(</span>|v| v<span class=punctuation>.</span><span class=function>map</span><span class=punctuation>(</span>|v| <span class=operator>*</span>v<span class=punctuation>));</span>
    <span class=keyword>match</span> v <span class=punctuation>{</span>
       <span class=constructor>Some</span><span class=punctuation>(</span>v<span class=punctuation>)</span> => <span class=macro>println!</span><span class=punctuation>(</span><span class=string>"{v}"</span><span class=punctuation>),</span>
       <span class=constructor>None</span> => <span class=macro>println!</span><span class=punctuation>(</span><span class=string>"None"</span><span class=punctuation>),</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>

<span class=attribute>#</span><span class=punctuation>[</span><span class=attribute>tokio</span><span class=punctuation>::</span><span class=attribute>main</span><span class=punctuation>]</span>
<span class=keyword>async</span> <span class=keyword>fn</span> <span class=function>main</span><span class=punctuation>()</span> <span class=punctuation>{</span>
    <span class=comment>// Define a stack of wrapped tasks, showing how the task-local is saved/restored</span>
    <span class=function>print</span><span class=punctuation>();</span>    
    tokio<span class=punctuation>::</span><span class=function>spawn</span><span class=punctuation>(</span><span class=constructor>SOME_GLOBAL</span><span class=punctuation>.</span><span class=function>scope</span><span class=punctuation>(</span><span class=constant>5138008</span><span class=punctuation>,</span> <span class=keyword>async</span> <span class=punctuation>{</span>
        <span class=function>print</span><span class=punctuation>();</span>
        tokio<span class=punctuation>::</span><span class=function>spawn</span><span class=punctuation>(</span><span class=constructor>SOME_GLOBAL</span><span class=punctuation>.</span><span class=function>scope</span><span class=punctuation>(</span><span class=constant>69</span><span class=punctuation>,</span> <span class=keyword>async</span> <span class=punctuation>{</span>
            <span class=function>print</span><span class=punctuation>();</span>
            tokio<span class=punctuation>::</span><span class=function>spawn</span><span class=punctuation>(</span><span class=constructor>SOME_GLOBAL</span><span class=punctuation>.</span><span class=function>scope</span><span class=punctuation>(</span><span class=constant>42</span><span class=punctuation>,</span> <span class=keyword>async</span> <span class=punctuation>{</span>
                <span class=function>print</span><span class=punctuation>();</span>
            <span class=punctuation>})).</span><span class=keyword>await</span><span class=punctuation>.</span><span class=function>unwrap</span><span class=punctuation>();</span>
            <span class=function>print</span><span class=punctuation>();</span>
        <span class=punctuation>})).</span><span class=keyword>await</span><span class=punctuation>.</span><span class=function>unwrap</span><span class=punctuation>();</span>
        <span class=function>print</span><span class=punctuation>();</span>
    <span class=punctuation>})).</span><span class=keyword>await</span><span class=punctuation>.</span><span class=function>unwrap</span><span class=punctuation>();</span>
    <span class=function>print</span><span class=punctuation>();</span>
<span class=punctuation>}</span></code></pre><p>Note the <code>tokio::spawn()</code> calls around each <code>.scope()</code> call (to properly test what happens when there are multiple Tasks running concurrently) but NOT around each <code>async {}</code> call (to ensure the <code>print()</code> calls happen in the same Task as their surrounding <code>.scope()</code>).</p><p>Trying this out at the <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=02c3f51b299f07ac860e28dd16933c1d">Rust playground</a>, we observe the following output:</p><pre class=syntax-highlighting><code>None
5138008
69
42
69
5138008
None
</code></pre><p>just like we drew it up :)</p><h2><a aria-hidden=true class=anchor href=#conclusion id=heading-conclusion></a>Conclusion</h2><p>In this post, I've shown a complete(!!) way of writing Task-Locals in Rust, using only the stdlib, that covers the "multiple Tasks on one Thread" case (via swapping from stack to Thread-Local), as well as the "single Task on multiple Threads" case; <a href=https://doc.rust-lang.org/std/thread/struct.LocalKey.html><code>std::thread::LocalKey</code></a> stands for "how we access the current Thread's TLS", so the resulting <code>Scoped</code> future can, in fact, be sent to other threads, where it will simply use the TLS of whatever Thread it starts running on.</p><p>The source code in this post is directly copied from <a href=https://git.sr.ht/~polywolf/driver/blob/725e822d8302ecf1af290aab9582c357c56f9e2a/packages/async-task-local/src/lib.rs>part of a larger collection of crates I'm making</a>, and while I might eventually consider publishing it to crates.io, it's small enough to vendor in any project if you really want it.</p><p>Anyways that's all, hope you enjoyed & maybe even learned something! Until next time!!</p><section class=footnotes data-footnotes><ol><li id=fn-other-reading><p>I considered linking to Kora's <a href=https://blog.cat-girl.gay/3ds-async-part-one/>Building an AsyncIO executor for the 3DS</a> instead of writing this whole intro section, since that post covers very similar ground, but hey one more explanation in the world can't hurt right? <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-other-reading>↩</a></p></li><li id=fn-gay><p>ex. Mutexes, Condition Variables, Semaphores, etc. I do not apologize for my habit of phrasing things as lewdly as possible, because if u think about it, <em>really</em> think about it, u too shall realize concurrency is yur. the exactly flavor depends per language: Rust => slow-burn, Go => toxic, C++ => doomed. i rest my case <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-gay>↩</a></p></li><li id=fn-thread-stacks><p>The concept of a stack and "what code is being executed" is too tightly linked for it to be any other way, at least with our current languages/computer architectures. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-thread-stacks>↩</a></p></li><li id=fn-os-slow><p>Yeah I'm not going to get into this. The reasons are <em>really</em> complex and I'll admit I don't understand them fully myself. Something something timer interrupts something something pipeline stalls something something <a href=https://en.wikipedia.org/wiki/Control_register#CR3><code>%cr3</code></a>?? A "TL;DR you should be aware of this" is enough. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-os-slow>↩</a></p></li><li id=fn-coroutine-green-thread><p>Note that this definition doesn't actually depend on if the Task is a Coroutine or a Green Thread! Yes, Rust Futures are actually Coroutines, and <code>poll()</code> methods tend to be indicative of that, but a Green Thread-based system could also expose an API with similar guarantees, likely for the purpose of constructing such scoped wrappers. <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-coroutine-green-thread>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Hal Formula: What Camera Does Daisuke Mitsuoka Use?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-06-14-hal-formula-what-camera-does-daisuke-mitsuoka-use-/" />
      <id>https://wolfgirl.dev/blog/2026-06-14-hal-formula-what-camera-does-daisuke-mitsuoka-use-/</id>
      <published>2026-06-15T00:00:23Z</published>
      <updated>2026-06-15T00:00:23Z</updated>
      <summary><![CDATA[This weekend, as usual, I found myself reading thru all of Shonen Jump. And yet again, there is another manga with photobased cameras in ...]]></summary>
      <content type="html"><![CDATA[<p>This weekend, as usual, I found myself reading thru all of Shonen Jump. And yet again, there is another manga with photobased cameras in it, so I just had to find out which one they used.</p><p>So this is a manga about racecars that go very fast. And it's a good enough first chapter<sup class=footnote-ref><a data-footnote-ref href=#fn-tn-note id=fnref-tn-note>1</a></sup> (yay he wins wahoo) but I am more interested in the side character that appears for 2 pages taking photos of the race, because he has a cool camera!!</p><h3><a aria-hidden=true class=anchor href=#panel-1 id=heading-panel-1></a>Panel 1</h3><p><img alt="Daisuke Mitsuoka, fictional photographer for the &#34;Lights Out!&#34; magazine, explains: &#34;WF1 is the world's premier racing league. Below that, there's 'WF2', 'WF3', and 'WF4' at the bottom. The feeder series showcase an ultimate showdown of pure skills using the cars with the same specifications. They're a gateway to success for those who aim to become professional racers.&#34;" src=https://static.wolfgirl.dev/polywolf/blog/019ec87c-485b-7664-94fc-3e03b5b1359d/IMG_3637.jpeg></p><h3><a aria-hidden=true class=anchor href=#panel-2 id=heading-panel-2></a>Panel 2</h3><p><img alt="Daisuke, now looking into the camera lens, further explains over the roar of the nearby cars: &#34;Still, 230 kilometers per hour. That's 100 meters in 1.5 seconds... It's a crazy world of speed!&#34;" src=https://static.wolfgirl.dev/polywolf/blog/019ec87c-485b-7664-94fc-3e03b5b1359d/IMG_3638.jpeg></p><p>My goal for today is to find out exactly what gear he's using.</p><h2><a aria-hidden=true class=anchor href=#the-lens id=heading-the-lens></a>The Lens</h2><p>From these photos, we observe the lens MUST have the following traits:</p><ol><li>Is Very Long</li><li>Has the little thing that sticks out on the bottom for mounting</li><li>Outermost cone flares out a bit</li><li>Comes in a white-colored variant</li></ol><p>(4) is obvious, and turns out the rest map cleanly to filters too:</p><ol><li>We're looking for a "Telephoto"- or "SuperTelephoto"-class lens</li><li>We're looking for a lens with a "foot mount"</li><li>We're looking for a "Prime" lens; all "Zoom" lenses tend to be perfect cylinders to make the zoom optics easier</li></ol><p>Browsing online stores like B&H Photo that sell All Lenses, we come across two likely candiates: the <a href=https://www.bhphotovideo.com/c/product/1634485-REG/canon_5054c002_rf_600mm_f_4l_is.html>Canon RF 600mm F4 L IS USM</a><sup class=footnote-ref><a data-footnote-ref href=#fn-canon id=fnref-canon>2</a></sup> & the <a href=https://www.bhphotovideo.com/c/product/1485539-REG/sony_sel600f40gm_fe_600mm_f_4_gm.html>Sony FE 600mm f/4 GM OSS Lens</a><sup class=footnote-ref><a data-footnote-ref href=#fn-sony id=fnref-sony>3</a></sup>:</p><div class=table-wrapper><table><thead><tr><th></th><th>Front View</th><th>Side View</th></tr></thead><tbody><tr><td><strong>Canon</strong></td><td><img alt="Canon 600mm lens, 3/4 view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_600mm_34.webp></td><td><img alt="Canon 600mm lens, side view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_600mm_side.webp></td></tr><tr><td><strong>Sony</strong></td><td><img alt="Sony 600mm lens, 3/4 view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/sony_600mm_34.jpg></td><td><img alt="Sony 600mm lens, side view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/sony_600mm_side.jpg></td></tr></tbody></table></div><p>Given what we can observe, I claim the Canon lines up better:</p><ol><li>The front is more uniformly conical.</li><li>Hard to see, but the side knob is <em>behind</em> the button group instead of in front.</li></ol><p>Also, we should probably be sure this is actually the 600mm Canon; there are various other telephoto Canon lenses of varying sizes that it could be right? Well, let's take a look at the other 3:</p><div class=table-wrapper><table><thead><tr><th></th><th>Short Cone</th><th>Long Cone</th></tr></thead><tbody><tr><td><strong>Short Mount</strong></td><td><img alt="Canono 400mm lens, side profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_400mm_side.webp><br><a href="https://www.usa.canon.com/shop/p/rf400mm-f2-8-l-is-usm?type=New">400mm</a></td><td><img alt="Canon 600mm lens, side view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_600mm_side.webp> 600mm</td></tr><tr><td><strong>Long Mount</strong></td><td><img alt="Canon 800mm lens, side profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_800mm_side.webp><br><a href="https://www.usa.canon.com/shop/p/rf800mm-f5-6-l-is-usm-lens?type=New">800mm</a></td><td><img alt="Canon 1200mm lens, side profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_1200mm_side.webp><br><a href="https://www.usa.canon.com/shop/p/rf1200mm-f8-l-is-usm-lens?type=New">1200mm</a></td></tr></tbody></table></div><p>Looking at all these, it seems the 400mm actually fits the observed button layout in the second panel the best: the "long mount" lenses (with those built-in zoom doublers) are much too long when compared to the second manga panel, and the 600mm also has an extra ring, so only the 400mm is short enough to include all the buttons that are shown. I guess the perspective in the first shot is fake, or there is some other non-straight hood I don't know about?</p><p><img alt="Canon 400mm lens, 3/4 view with a long hood attached" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_400mm_34_hood.webp></p><p>Anyways all's well that ends well, the <strong>Canon RF 400mm f/2.8L IS USM</strong> is a near-exact match for what's shown in the manga.</p><p>...</p><p>"But wait!!!" I hear you yell, "Haven't you forgotten about the <a href="https://www.usa.canon.com/shop/p/ef-400mm-f-2-8l-is-iii-usm?type=New">Canon EF 400mm f/2.8L IS III USM</a> lens, which looks almost identical?" And yes, I have considered that, but in order to explain why, we'll need to look at the camera body too.</p><h2><a aria-hidden=true class=anchor href=#the-body id=heading-the-body></a>The Body</h2><p>Given we almost certainly have a Canon lens, we're also very likely to have a Canon camera body. Compared to <a href=https://wolfgirl.dev/blog/2026-04-27-what-camera-do-tenichi-honoka-use-in-hima-ten-/>previous camera searches</a>, this one is a lot easier because of one key feature on the camera: <em>it is tall</em>, much taller than an ordinary Canon camera, meaning it is top-of-line professional-grade stuff. (As expected of a press sports photographer lol.)</p><p>Of the available Canon bodies that exist, the following are potential matches:</p><div class=table-wrapper><table><thead><tr><th></th><th><a href=https://www.usa.canon.com/shop/p/eos-r1>EOS R1</a></th><th><a href="https://www.usa.canon.com/shop/p/eos-r3?color=Black&type=New">EOS R3</a></th><th><a href=https://www.usa.canon.com/shop/p/refurbished-eos-1d-x-mark-iii-body>EOS-1D X Mark III</a></th></tr></thead><tbody><tr><td>Front Profile</td><td><img alt="Canon EOS R1 front profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_r1_front.webp></td><td><img alt="Canon EOS R3 front profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_r3_front.webp></td><td><img alt="Canon EOS-1D X Mark III front profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_1d_x_mark_iii_front.webp></td></tr><tr><td>Side Profile</td><td><img alt="Canon EOS R1 side profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_r1_side.webp></td><td><img alt="Canon EOS R3 side profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_r3_side.webp></td><td><img alt="Canon EOS-1D X Mark III side profile" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_1d_x_mark_iii_side.jpg></td></tr></tbody></table></div><p>And of these, the EOS R1 side profile visually fits best, especially the dimensions of the viewfinder. This choice makes sense in-context too, because at the time of writing, the EOS R1 is the "best" Canon camera money can buy, for photography at least<sup class=footnote-ref><a data-footnote-ref href=#fn-best id=fnref-best>4</a></sup>.</p><p>So, because the camera is a <strong>Canon EOS R1</strong>, which is a mirrorless camera, we can be very confident he is using a mirrorless lens for it too, closing our earlier argument.</p><h2><a aria-hidden=true class=anchor href=#bonus-the-camera-on-his-hip id=heading-bonus-the-camera-on-his-hip></a>Bonus: The Camera On His Hip</h2><p>You can hardly even see it, but in the first image Daisuke also has a camera strapped around his body lying on his hip. You can't make out much except the top panel, but I claim that's enough for us!!</p><p>The reasoning is, we can make out the following features:</p><ol><li>Information display is rectangular</li><li>Dial is on the right (when looking at the lens)</li><li>(Assumption) It's another Canon, based on boot shape + being in the Canon ecosystem</li></ol><p>Just these two features alone are enough to rule out <em>all of Canon's mirrorless offerings</em>. Those all have more square-ish information screens, and all the high-end ones have the dial on the left, taking the <a href="https://www.usa.canon.com/shop/p/eos-r5?color=Black&type=New">EOS R5</a> as an example: <img alt="Canon EOS R5, top view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_r5_top.webp></p><p>Instead, I think this is a <a href="https://www.usa.canon.com/shop/p/eos-5d-mark-iv?color=Black&type=New"><strong>Canon EOS 5D Mark IV</strong></a>, the last & best DSLR Canon made, whose top does match pretty well:</p><p><img alt="Canon EOS 5D Mark IV, top view" src=https://static.wolfgirl.dev/polywolf/blog/019ec869-ac76-7ff0-b1b3-776b97166a35/canon_eos_5d_mark_iv_top.webp></p><p>I think it's that one instead of another one because, given the rest of his gear, no way a press photography would settle for anything other than the best.</p><h2><a aria-hidden=true class=anchor href=#double-bonus-conspiracy-theory id=heading-double-bonus-conspiracy-theory></a>Double Bonus: Conspiracy Theory</h2><p>Either Canon has a massive mindshare among the Japanese people (unlikely, I've seen plenty of Nikon & Sony at press conferences n such), or there is some sort of agreement with Canon that allows them to use their cameras in manga...</p><section class=footnotes data-footnotes><ol><li id=fn-tn-note><p>TN note: I am almost certain they translated the title incorrectly? There is a reference in the text to "haru", meaning spring, a syllable in the names of both the main racer guy & his younger brother, so I can definitely see "Haru Formula" being mistranslated like this. Anyways, too late to change it now... <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-tn-note>↩</a></p></li><li id=fn-canon><p>"Canon RF" = mount type, specifically for their latest line of full-frame mirrorless cameras w/ a slightly shorter focal distance than their previous EF line for DSLRs. "600mm" = effective focal length when compared to an "ideal" lens setup w/ <code>N</code>mm from lens to mount & 35mm from mount to sensor. Really what it means is effective zoom factor, not actual dimensions of the lens; as an example, the iPhone 16 Pro Max "5x zoom" camera is an "120mm" camera, because even though it physically measures just 15.66mm, the actual lens geometry means the effective zoom is that much more. "F4" = "maximum aperture", which ironically is the smallest F-stop number, basically how "wide-open" can the lens be. "L" = "luxury", meaningless except as a marketing term. "IS" = "image stabilization", means there is extra mechanics inside the lens to correct for small shakes while taking a photo, very important with lenses this long. "USM" = "ultra-sonic motor", describes the autofocus system, usually means it's fast + quiet. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-canon>↩</a></p></li><li id=fn-sony><p>Most of these terms have basically the same meaning as for Canon's lens, with "GM" meaning the same thing as "L" & "OSS" = "Optical SteadyShot" meaning the same thing as "IS". <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-sony>↩</a></p></li><li id=fn-best><p>Technically, it loses out to the Canon EOS 5D Mark IV when it comes to raw megapixel count (50.3MP vs 30.4MP), but "being mirrorless" + "being newer" means it brings a whole bunch of other features that win out for practical use, "faster automatic subject tracking" likely being one very relevant to racing photography in particular. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-best>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[The Backrooms Movie]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-06-04-the-backrooms-movie/" />
      <id>https://wolfgirl.dev/blog/2026-06-04-the-backrooms-movie/</id>
      <published>2026-06-05T03:43:14Z</published>
      <updated>2026-06-05T03:43:14Z</updated>
      <summary><![CDATA[Yeah ok that was really really good, lived up to the hype and then some. I read another review before this that said something along the ...]]></summary>
      <content type="html"><![CDATA[<p>Yeah ok that was really really good, lived up to the hype and then some. I read another review before this that said something along the lines of:</p><blockquote><p>What "The Backrooms" means to Millennials vs Zoomers is very different; while Millennials view those liminal spaces as once-bustling & having lost their purpose, Zoomers view them as always having been deserted. In the former interpretation, the creepyness comes from the fact that they're deserted & inherently wrong-looking while empty. In the latter, the creepyness comes from the fact that these spaces were built at all despite "not having a purpose". Therefore, it makes more sense to put a spooky monster if you're going for the latter interpretation; in the former, it just ruins the idea of the space being empty.</p></blockquote><p>(not an exact quote my any means, just my recollection)</p><p>This movie does lend itself more toward the latter interpretation (being originally conceived & now directed by a Zoomer will do that), but I think it understands the former perspective as well. There are motivated reasons for <em>why</em> the spooky monster is in there that fit in with the overarching theme of "Backrooms is a physical reflection of society's unconscious remembering/forgetting". What it <em>means</em> for such a place to exist still eludes me, even after watching the movie, but the concepts r juicy & fun to munch on so i dont mind :)</p><p>Also mad shoutouts to the 3D design team, the myriad VFX teams, & the rotoscoping teams. Making this entirely inside Blender is no easy feat but they pulled it off flawlessly, immaculate vibes/aesthetic, as should be expected from a product that hinges so heavily on aesthetic to convey meaning. Oh and also the sound team, good use of subsonics without being <em>too</em> overbearing w/ it.</p><p>Overall there was a lot of tension but I had a great time, scary enough 4 me 2 get my stomach excited but not too scary that i cant watch. 4/5 would highly recommend if you liked the youtube videos</p><p>Also also terrible idea to go into One Of The Most Backroom-y subway stations immediately afterwards... 😰</p><p><img alt="An empty subway station with a very long curved corridor" src=https://static.wolfgirl.dev/polywolf/blog/019e95d8-0075-7550-9ec0-a22aab529f3d/IMG_3582.jpeg></p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Erasing Existentials]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-05-20-erasing-existentials/" />
      <id>https://wolfgirl.dev/blog/2026-05-20-erasing-existentials/</id>
      <published>2026-05-20T13:43:32Z</published>
      <updated>2026-05-20T13:43:32Z</updated>
      <summary><![CDATA[I got nerd-sniped hard, so I wrote a blog post about how to type-erase existential quantifiers in Rust.]]></summary>
      <content type="html"><![CDATA[<p>Recently, I got nerd-sniped <em>hard</em> by <a href=https://bsky.app/profile/welltypedwit.ch/post/3mlpwmdubds2i>a post made by</a> <a href=https://welltypedwit.ch/>Alice (@welltypedwit.ch)</a>. In it, she asks:</p><blockquote><p>So, in Rust, <code>dyn Trait</code> means <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>s</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi></mrow><annotation encoding=application/x-tex>\exists s. \text{Trait}(s) \land s</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal">s</span></span></span></span> , and <code>fn f() -> impl Trait</code> means <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>f</mi><mo>:</mo><mi mathvariant=normal>∃</mi><mi>s</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo stretchy=false>)</mo><mo>∧</mo><mo stretchy=false>(</mo><mo stretchy=false>(</mo><mo stretchy=false>)</mo><mo>→</mo><mi>s</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>f: \exists s. \text{Trait}(s) \land (() \rightarrow s)</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.1944em;height:.8889em></span><span class="mord mathnormal" style=margin-right:.1076em>f</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>:</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>((</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">s</span><span class=mclose>)</span></span></span></span> , but what if I want an existential over something other than the <code>Self</code> parameter? Like, what if I have <code>Trait&lt;B></code> and I want an <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi></mrow><annotation encoding=application/x-tex>\exists s, b. \text{Trait}(s, b) \land s</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal">s</span></span></span></span> ?</p></blockquote><p>That is a lot of fancy math symbols, but I hope by the end of this you will understand both what they mean and my answer to this question!</p><h2><a aria-hidden=true class=anchor href=#what-is-an-existential-quantifier id=heading-what-is-an-existential-quantifier></a>What Is An Existential Quantifier?</h2><p>A hint can be found in the first part of her question:</p><blockquote><p><code>dyn Trait</code> means <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>s</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi></mrow><annotation encoding=application/x-tex>\exists s. \text{Trait}(s) \land s</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal">s</span></span></span></span></p></blockquote><p>Let's think about what it means to have a value of type <a href=https://doc.rust-lang.org/reference/types/trait-object.html><code>dyn Trait</code></a><sup class=footnote-ref><a data-footnote-ref href=#fn-sized id=fnref-sized>1</a></sup>. We know there is <em>some</em> underlying type, but we don't have a way of getting at that type. All we can do is call methods from <code>Trait</code>.</p><p>That's pretty much exactly what that formula says! "There exists ( <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> ) some type <code>s</code>, such that ( <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>.</mi></mrow><annotation encoding=application/x-tex>.</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.1056em></span><span class=mord>.</span></span></span></span> ) <code>s</code> conforms to <code>Trait</code>, and ( <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mo>∧</mo></mrow><annotation encoding=application/x-tex>\land</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.5556em></span><span class=mord>∧</span></span></span></span> ) you can use <code>s</code> right now". The scary math symbols are just a concise way of writing that. Math also helps us formalize the fact that, given just this <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> statement, we have no way of knowing what <code>s</code> will be ahead of time, so in constructing a "proof" that the program is well-typed, we can only assume things about <code>s</code> that come from it conforming to <code>Trait</code><sup class=footnote-ref><a data-footnote-ref href=#fn-dyn id=fnref-dyn>2</a></sup>.</p><p>Alice also points out another place Rust has something that looks like an existential quantifier:</p><blockquote><p><code>fn f() -> impl Trait</code> means <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>f</mi><mo>:</mo><mi mathvariant=normal>∃</mi><mi>s</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo stretchy=false>)</mo><mo>∧</mo><mo stretchy=false>(</mo><mo stretchy=false>(</mo><mo stretchy=false>)</mo><mo>→</mo><mi>s</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>f: \exists s. \text{Trait}(s) \land (() \rightarrow s)</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.1944em;height:.8889em></span><span class="mord mathnormal" style=margin-right:.1076em>f</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>:</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>((</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">s</span><span class=mclose>)</span></span></span></span></p></blockquote><p>This "<a href=https://doc.rust-lang.org/reference/types/impl-trait.html#r-type.impl-trait.return>return-position impl trait</a>" syntax is indeed another way of specifying a type such that callers of <code>f</code> only know about the fact that the return type conforms to <code>Trait</code>. Unlike with <code>dyn Trait</code>, these are fixed at compile time, but type-theoretically they're somewhat similar.</p><p>So! Now that we have a bit of understanding, let's get started on the real question:</p><blockquote><p>What if I have <code>Trait&lt;B></code> and I want an <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi></mrow><annotation encoding=application/x-tex>\exists s,b. \text{Trait}(s, b) \land s</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal">s</span></span></span></span> ?</p></blockquote><p>That is, is it possible to have an existentially quantified type <code>S</code> that itself has another existentially quantified type variable <code>B</code>, such that <code>S: Trait&lt;B></code>?</p><h2><a aria-hidden=true class=anchor href=#attempt-1-for-exists-conversion id=heading-attempt-1-for-exists-conversion></a>Attempt 1: For-Exists Conversion</h2><p>A classic way of dealing with <em>any</em> existentials is something I call "for-exists conversion". Basically, it relies on the following identity:</p><p><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mo stretchy=false>(</mo><mo stretchy=false>(</mo><mi mathvariant=normal>∃</mi><mi>x</mi><mi mathvariant=normal>.</mi><mi>P</mi><mo stretchy=false>(</mo><mi>x</mi><mo stretchy=false>)</mo><mo stretchy=false>)</mo><mo>→</mo><mi>Q</mi><mo stretchy=false>)</mo><mo>⟺</mo><mo stretchy=false>(</mo><mi mathvariant=normal>∀</mi><mi>x</mi><mi mathvariant=normal>.</mi><mo stretchy=false>(</mo><mi>P</mi><mo stretchy=false>(</mo><mi>x</mi><mo stretchy=false>)</mo><mo>→</mo><mi>Q</mi><mo stretchy=false>)</mo><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex> ((\exists x.P(x)) \rightarrow Q) \Longleftrightarrow (\forall x.(P(x) \rightarrow Q)) </annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>((</span><span class=mord>∃</span><span class="mord mathnormal">x</span><span class=mord>.</span><span class="mord mathnormal" style=margin-right:.1389em>P</span><span class=mopen>(</span><span class="mord mathnormal">x</span><span class=mclose>))</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">Q</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>⟺</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(</span><span class=mord>∀</span><span class="mord mathnormal">x</span><span class=mord>.</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.1389em>P</span><span class=mopen>(</span><span class="mord mathnormal">x</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">Q</span><span class=mclose>))</span></span></span></span></p><details><summary>Type theory friend</summary> <p>My type theory friend said "this is just currying lol" and I was very confused, because the definition for currying I'm familiar with is</p> <p><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mo stretchy=false>(</mo><mo stretchy=false>⟨</mo><mi>a</mi><mo separator=true>,</mo><mi>b</mi><mo stretchy=false>⟩</mo><mo>→</mo><mi>c</mi><mo stretchy=false>)</mo><mo>⟺</mo><mo stretchy=false>(</mo><mi>a</mi><mo>→</mo><mi>b</mi><mo>→</mo><mi>c</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex> (\langle a, b \rangle \rightarrow c) \Longleftrightarrow (a \rightarrow b \rightarrow c) </annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(⟨</span><span class="mord mathnormal">a</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mclose>⟩</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">c</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>⟺</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(</span><span class="mord mathnormal">a</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=height:.6944em></span><span class="mord mathnormal">b</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">c</span><span class=mclose>)</span></span></span></span></p> <p>That is, given a function taking a tuple, you can make it into a function returning a function, which at first glance looks nothing like the exists/forall theorem stated above.</p> <p>However! If you have a "dependent sum" <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><msub><mi mathvariant=normal>Σ</mi><mi>α</mi></msub><mi>β</mi><mo stretchy=false>(</mo><mi>α</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>\Sigma_\alpha \beta(\alpha)</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord><span class=mord>Σ</span><span class=msupsub><span class="vlist-t vlist-t2"><span class=vlist-r><span class=vlist style=height:.1514em><span style=margin-left:0;margin-right:.05em;top:-2.55em><span class=pstrut style=height:2.7em></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style=margin-right:.0037em>α</span></span></span></span><span class=vlist-s>​</span></span><span class=vlist-r><span class=vlist style=height:.15em><span></span></span></span></span></span></span><span class="mord mathnormal" style=margin-right:.0528em>β</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mclose>)</span></span></span></span> (type of second element <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>β</mi></mrow><annotation encoding=application/x-tex>\beta</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.1944em;height:.8889em></span><span class="mord mathnormal" style=margin-right:.0528em>β</span></span></span></span> depends on value of first element <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>α</mi></mrow><annotation encoding=application/x-tex>\alpha</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal" style=margin-right:.0037em>α</span></span></span></span> ) and a "dependent product" <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><msub><mi mathvariant=normal>Π</mi><mi>α</mi></msub><mi>β</mi><mo stretchy=false>(</mo><mi>α</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>\Pi_\alpha \beta(\alpha)</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord><span class=mord>Π</span><span class=msupsub><span class="vlist-t vlist-t2"><span class=vlist-r><span class=vlist style=height:.1514em><span style=margin-left:0;margin-right:.05em;top:-2.55em><span class=pstrut style=height:2.7em></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style=margin-right:.0037em>α</span></span></span></span><span class=vlist-s>​</span></span><span class=vlist-r><span class=vlist style=height:.15em><span></span></span></span></span></span></span><span class="mord mathnormal" style=margin-right:.0528em>β</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mclose>)</span></span></span></span> (function where the return type <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>β</mi></mrow><annotation encoding=application/x-tex>\beta</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.1944em;height:.8889em></span><span class="mord mathnormal" style=margin-right:.0528em>β</span></span></span></span> depends on the value of the input <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>α</mi></mrow><annotation encoding=application/x-tex>\alpha</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal" style=margin-right:.0037em>α</span></span></span></span> ), then you get <a href=https://ncatlab.org/nlab/show/existential+quantifier#in_dependent_type_theory>a very similar-looking identity</a><sup class=footnote-ref><a data-footnote-ref href=#fn-nlab id=fnref-nlab>3</a></sup>:</p> <p><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mo stretchy=false>(</mo><mo stretchy=false>(</mo><msub><mi mathvariant=normal>Σ</mi><mi>α</mi></msub><mi>β</mi><mo stretchy=false>(</mo><mi>α</mi><mo stretchy=false>)</mo><mo stretchy=false>)</mo><mo>→</mo><mi>C</mi><mo stretchy=false>)</mo><mo>⟺</mo><mo stretchy=false>(</mo><msub><mi mathvariant=normal>Π</mi><mi>α</mi></msub><mi>β</mi><mo stretchy=false>(</mo><mi>α</mi><mo stretchy=false>)</mo><mo>→</mo><mi>C</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex> ((\Sigma_\alpha \beta(\alpha)) \rightarrow C) \Longleftrightarrow (\Pi_\alpha \beta(\alpha) \rightarrow C) </annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>((</span><span class=mord><span class=mord>Σ</span><span class=msupsub><span class="vlist-t vlist-t2"><span class=vlist-r><span class=vlist style=height:.1514em><span style=margin-left:0;margin-right:.05em;top:-2.55em><span class=pstrut style=height:2.7em></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style=margin-right:.0037em>α</span></span></span></span><span class=vlist-s>​</span></span><span class=vlist-r><span class=vlist style=height:.15em><span></span></span></span></span></span></span><span class="mord mathnormal" style=margin-right:.0528em>β</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mclose>))</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal" style=margin-right:.0715em>C</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>⟺</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(</span><span class=mord><span class=mord>Π</span><span class=msupsub><span class="vlist-t vlist-t2"><span class=vlist-r><span class=vlist style=height:.1514em><span style=margin-left:0;margin-right:.05em;top:-2.55em><span class=pstrut style=height:2.7em></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style=margin-right:.0037em>α</span></span></span></span><span class=vlist-s>​</span></span><span class=vlist-r><span class=vlist style=height:.15em><span></span></span></span></span></span></span><span class="mord mathnormal" style=margin-right:.0528em>β</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal" style=margin-right:.0715em>C</span><span class=mclose>)</span></span></span></span></p> <p>You can interpret both sides either way:</p> <ul><li>In the "types as propositions" interpretation, <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>Σ</mi></mrow><annotation encoding=application/x-tex>\Sigma</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6833em></span><span class=mord>Σ</span></span></span></span> is <em>kind of</em> like <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> in that, for it to be inhabited, you only need a single <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mo stretchy=false>⟨</mo><mi>α</mi><mo separator=true>,</mo><mi>β</mi><mo stretchy=false>(</mo><mi>α</mi><mo stretchy=false>)</mo><mo stretchy=false>⟩</mo></mrow><annotation encoding=application/x-tex>\langle \alpha, \beta(\alpha) \rangle</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>⟨</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal" style=margin-right:.0528em>β</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mclose>)⟩</span></span></span></span> pair, and <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>Π</mi></mrow><annotation encoding=application/x-tex>\Pi</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6833em></span><span class=mord>Π</span></span></span></span> is <em>kind of</em> like <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∀</mi></mrow><annotation encoding=application/x-tex>\forall</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∀</span></span></span></span> in that, for it to be inhabited, you need to show how to get a <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>β</mi><mo stretchy=false>(</mo><mi>α</mi><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>\beta(\alpha)</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal" style=margin-right:.0528em>β</span><span class=mopen>(</span><span class="mord mathnormal" style=margin-right:.0037em>α</span><span class=mclose>)</span></span></span></span> for every possible <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>α</mi></mrow><annotation encoding=application/x-tex>\alpha</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal" style=margin-right:.0037em>α</span></span></span></span> .</li><li>Interpreting them as datastructures, <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>Σ</mi></mrow><annotation encoding=application/x-tex>\Sigma</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6833em></span><span class=mord>Σ</span></span></span></span> is just a tuple, and <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>Π</mi></mrow><annotation encoding=application/x-tex>\Pi</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6833em></span><span class=mord>Π</span></span></span></span> is just a function, which is exactly what our original currying looks like.</li></ul> <p>So, my type theory friend argues "you should call this 'currying' instead of making up a new name". But I don't agree with that since the connection is not obvious, so I will stick with my own name for the sake of clarity :) The more you know!</p></details><p>In Rust, this can be stated as:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>f_left</span><span class=punctuation>(</span><span class=variable>x</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>P</span><span class=punctuation>)</span> -> <span class=type>Q</span> <span class=punctuation>{</span>
    x<span class=punctuation>.</span><span class=function>foobar</span><span class=punctuation>()</span>
<span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>f_right</span><span class=punctuation>&lt;</span><span class=type>X</span><span class=punctuation>:</span> <span class=type>P</span><span class=punctuation>>(</span><span class=variable>x</span><span class=punctuation>:</span> <span class=type>X</span><span class=punctuation>)</span> -> <span class=type>Q</span> <span class=punctuation>{</span>
    x<span class=punctuation>.</span><span class=function>foobar</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>"But PolyWolf!" I hear you say, "You've just written the same function twice??" And that, dear reader, is exactly what's so sneaky about this identity. It <em>seems</em> obvious, but it's actually saying something pretty profound.</p><p><code>f_left</code> really does use an existential quantifier for its argument (all you know about <code>x</code> is that it has a type conforming to <code>P</code>), while <code>f_right</code> first quantifies over all possible types for <code>X</code>, then restricts to those that satisfy the trait <code>P</code>. In Rust, you might think the former is "just sugar for" the latter, but that doesn't change the fact these are <em>different mathematical statements</em>.</p><p>To see this more clearly, let's think about what happens when we use those argument types as return types instead:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>g1</span><span class=punctuation>()</span> -> <span class=keyword>impl</span> <span class=type>P</span> <span class=punctuation>{</span> <span class=type>SpecificX</span><span class=punctuation>::</span><span class=function>proof</span><span class=punctuation>()</span> <span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>g2</span><span class=punctuation>&lt;</span><span class=type>X</span><span class=punctuation>:</span> <span class=type>P</span><span class=punctuation>>()</span> -> <span class=type>X</span> <span class=punctuation>{</span> <span class=type>X</span><span class=punctuation>::</span><span class=function>proof</span><span class=punctuation>()</span> <span class=punctuation>}</span></code></pre><p>Here, we can more clearly see that <code>impl P</code> and <code>X: P</code> are saying different things. The former, like <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> , only requires you to demonstrate a <code>SpecificX</code> that satisfies the trait <code>P</code>, while the latter, like <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∀</mi></mrow><annotation encoding=application/x-tex>\forall</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∀</span></span></span></span> , requires you to produce valid values for all possible <code>X: P</code>.</p><p>So! Getting back to our original problem, we want to describe a type of the form:</p><p><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi></mrow><annotation encoding=application/x-tex>\exists s, b. \text{Trait}(s, b) \land s</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=height:.4306em></span><span class="mord mathnormal">s</span></span></span></span> For the rest of this post, we're going to assume that <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mtext>Trait(s, b)</mtext></mrow><annotation encoding=application/x-tex>\text{Trait(s, b)}</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span><span class=mord>(</span><span class=mord>s</span><span class=mord>,</span><span class=mord> </span><span class=mord>b</span><span class=mord>)</span></span></span></span></span> is some <code>S: Generic&lt;B></code> (renamed to avoid confusion w/ other traits introduced later), and that we can only build new types/traits that reference it, never modifying it or the following impls:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>pub</span> <span class=keyword>trait</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>name</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span><span class=punctuation>;</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>f32</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>i32</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>name</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span> <span class=string>"f32"</span> <span class=punctuation>}</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>f64</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>i32</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>name</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span> <span class=string>"f64"</span> <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Restricting ourselves first to functions that take in such a type, we get:</p><p><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mo stretchy=false>(</mo><mi mathvariant=normal>∃</mi><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mi mathvariant=normal>.</mi><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi><mo stretchy=false>)</mo><mo>→</mo><mo stretchy=false>(</mo><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>(\exists s, b. \text{Trait}(s, b) \land s) \rightarrow ()</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(</span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mord>.</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">s</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(</span><span class=mclose>)</span></span></span></span></p><p>Then, using our identity, we can rewrite it as:</p><p><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∀</mi><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mi mathvariant=normal>.</mi><mo stretchy=false>(</mo><mtext>Trait</mtext><mo stretchy=false>(</mo><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mo stretchy=false>)</mo><mo>∧</mo><mi>s</mi><mo stretchy=false>)</mo><mo>→</mo><mo stretchy=false>(</mo><mo stretchy=false>)</mo></mrow><annotation encoding=application/x-tex>\forall s, b. (\text{Trait}(s, b) \land s) \rightarrow ()</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mord>∀</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mord>.</span><span class=mopen>(</span><span class="mord text"><span class=mord>T</span><span class=mord>r</span><span class=mord>a</span><span class=mord>i</span><span class=mord>t</span></span><span class=mopen>(</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mclose>)</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>∧</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class="mord mathnormal">s</span><span class=mclose>)</span><span class=mspace style=margin-right:.2778em></span><span class=mrel>→</span><span class=mspace style=margin-right:.2778em></span></span><span class=base><span class=strut style=vertical-align:-.25em;height:1em></span><span class=mopen>(</span><span class=mclose>)</span></span></span></span></p><p>This results in the following function (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5d4a408cb9e2b259c311f65fd8de2c6c">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>use_generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>S</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
    s<span class=punctuation>.</span><span class=function>name</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>Easy peasy! This is a very standard way of dealing with the problem, and imo the most clean. However, <a href=https://bsky.app/profile/welltypedwit.ch/post/3mlqosj7his2o>Alice was not satisfied</a>:</p><blockquote><p>You couldn't e.g. put this one in a data structure (behind a <code>Box</code>) though, could you?</p></blockquote><p>And that's entirely true! Due to the way we rephrased the problem in terms of <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∀</mi></mrow><annotation encoding=application/x-tex>\forall</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∀</span></span></span></span> , once we return a <code>Box&lt;dyn Generic&lt;B>></code>, we'd have to keep a <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∀</mi><mi>b</mi></mrow><annotation encoding=application/x-tex>\forall b</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∀</span><span class="mord mathnormal">b</span></span></span></span> clause around on everything that uses it, which sort of defeats the purpose of having <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> types in the first place (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=7889714d44805f2f7f35a7a36a4f8e85">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>put_in_box</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> + <span class=operator>'</span><span class=label>static</span><span class=punctuation>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=type>S</span><span class=punctuation>)</span> -> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>></span> <span class=punctuation>{</span>
    <span class=type>Box</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>(</span>s<span class=punctuation>)</span>
<span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>use_box</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
    s<span class=punctuation>.</span><span class=function>name</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>This solution may be satisfactory for some usecases, but we'll have to try a bit harder to get things to only use <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> <span></span>.</p><h2><a aria-hidden=true class=anchor href=#attempt-2-associated-types id=heading-attempt-2-associated-types></a>Attempt 2: Associated Types</h2><p>My next idea was to "smuggle" the <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>s</mi><mo separator=true>,</mo><mi>b</mi><mi mathvariant=normal>.</mi></mrow><annotation encoding=application/x-tex>\exists s, b.</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.1944em;height:.8889em></span><span class=mord>∃</span><span class="mord mathnormal">s</span><span class=mpunct>,</span><span class=mspace style=margin-right:.1667em></span><span class="mord mathnormal">b</span><span class=mord>.</span></span></span></span> quantifier behind a single <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi><mi>t</mi><mi mathvariant=normal>.</mi></mrow><annotation encoding=application/x-tex>\exists t.</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span><span class="mord mathnormal">t</span><span class=mord>.</span></span></span></span> quantifier that Rust gives when using trait objects. A first crack at it might look something like (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=349cf08dc829d60b126b9f52b4f411ed">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>SingleAssociated</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>B</span><span class=punctuation>;</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>></span> <span class=type>SingleAssociated</span> <span class=keyword>for</span> <span class=type>S</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>B</span> = <span class=type>B</span><span class=punctuation>;</span>
<span class=punctuation>}</span></code></pre><p>However, we quickly run into a problem:</p><pre class=syntax-highlighting><code>error[E0207]: the type parameter `B` is not constrained by the impl trait, self type, or predicates
  --> src/main.rs:15:6
   |
15 | impl&lt;B, S: Generic&lt;B>> SingleAssociated for S {
   |      ^ unconstrained type parameter
</code></pre><p>The <a href=https://doc.rust-lang.org/error_codes/E0207.html>E0207 docs</a> do an excellent job explaining this (you should try <code>rustc --explain</code> if you haven't already!), but the short version is, whenever we're implementing traits, Rust wants there to be <em>at most one</em> concrete trait implementation for every concrete type. This restriction is called "coherence", because it would be "incoherent" if we could choose between multiple possible trait implementations for a single type<sup class=footnote-ref><a data-footnote-ref href=#fn-specialization id=fnref-specialization>4</a></sup>. When we write <code>impl&lt;B, S: Generic&lt;B>></code>, Rust considers all possible <code>B</code> and <code>S</code> that fit the criteria before moving onto the rest of the code. However, "all possible <code>B</code>" could result in more than one <code>SingleAssociated for S</code> implementations, so we are not allowed to do that.</p><p>To get around this, we'll have to put <code>B</code> in either the trait or type, to "constrain" it. We can't put it in the <code>SingleAssociated</code> trait definition (that would make it the same as <code>Generic&lt;B></code>!), and we can't put it in <code>S</code> (it's already generic), so what can we possibly do? <code>PhantomData</code> :)</p><p><a href=https://doc.rust-lang.org/std/marker/struct.PhantomData.html><code>PhantomData</code></a> is a zero-sized type that lets us to implement the <code>SingleAssociated</code> trait for something that, at runtime, is <em>basically</em> the same as a normal <code>S</code>, just with extra type-level information:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>use</span> std<span class=punctuation>::</span>marker<span class=punctuation>::</span><span class=constructor>PhantomData</span><span class=punctuation>;</span>

<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>></span> <span class=type>SingleAssociated</span> <span class=keyword>for</span> <span class=punctuation>(</span><span class=type>S</span><span class=punctuation>,</span> <span class=type>PhantomData</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>)</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>B</span> = <span class=type>B</span><span class=punctuation>;</span>
<span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>mk_existential</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=type>S</span><span class=punctuation>)</span> -> <span class=keyword>impl</span> <span class=type>SingleAssociated</span> <span class=punctuation>{</span>
    <span class=punctuation>(</span>s<span class=punctuation>,</span> <span class=constructor>PhantomData</span><span class=punctuation>)</span>
<span class=punctuation>}</span></code></pre><p>Great, mission accomplished, right? Except wait... how do we actually use it?? (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=522b821400e55e934d0e64545c6ed571">playground link</a>)</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>use_existential</span><span class=punctuation>(</span><span class=variable>_s</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>impl</span> <span class=type>SingleAssociated</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
    <span class=macro>todo!</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>The whole point of our existential type is to carry a proof about how we can do stuff with the <code>Generic</code> trait. But on its own, the <code>SingleAssociated</code> trait doesn't let us get at anything that implements <code>Generic&lt;B></code>. <em>We</em> know that it is only implemented for our special tuple type, but Rust doesn't, so we have to help it along by adding a second associated type + a function to get at it:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>DoubleAssociated</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>B</span><span class=punctuation>>;</span>
    <span class=keyword>type</span> <span class=type>B</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>as_ref</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>S</span><span class=punctuation>;</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>S</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>></span> <span class=type>DoubleAssociated</span> <span class=keyword>for</span> <span class=punctuation>(</span><span class=type>S</span><span class=punctuation>,</span> <span class=type>PhantomData</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>)</span>
<span class=keyword>where</span>
    <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>,</span>
<span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>S</span> = <span class=type>S</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>B</span> = <span class=type>B</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>as_ref</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>S</span> <span class=punctuation>{</span> <span class=operator>&</span><span class=variable>self</span><span class=punctuation>.</span><span class=constant>0</span> <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>This lets us use functions from <code>Generic&lt;B></code><sup class=footnote-ref><a data-footnote-ref href=#fn-self-types id=fnref-self-types>5</a></sup> (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=a423fefa5b7e2d3963de3d99f6998912">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>mk_existential</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=type>S</span><span class=punctuation>)</span> -> <span class=keyword>impl</span> <span class=type>DoubleAssociated</span> <span class=punctuation>{</span>
    <span class=punctuation>(</span>s<span class=punctuation>,</span> <span class=constructor>PhantomData</span><span class=punctuation>)</span>
<span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>use_existential</span><span class=punctuation>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>impl</span> <span class=type>DoubleAssociated</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
    s<span class=punctuation>.</span><span class=function>as_ref</span><span class=punctuation>().</span><span class=function>name</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>Great! If we were satisfied with using <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∃</mi></mrow><annotation encoding=application/x-tex>\exists</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∃</span></span></span></span> <span></span> types as returned from functions, we could stop here.</p><p>...However, to satisfy Alice's request, we'd also like to be able to "erase" that type by putting it in a <code>Box&lt;dyn Trait></code>, giving it a consistent representation; otherwise, two functions that both return <code>impl DoubleAssociated</code> could actually be returning different types, and e.g. couldn't be put inside the same <code>Vec</code>. We should able to do <code>Box&lt;dyn DoubleAssociated></code>, so let's try that, shall we? (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=7c335ebb94ff1628ef63e1ec483f6602">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>mk_box</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> + <span class=operator>'</span><span class=label>static</span><span class=punctuation>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=type>S</span><span class=punctuation>)</span> -> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>DoubleAssociated</span><span class=punctuation>&lt;</span><span class=type>S</span>=<span class=type>S</span><span class=punctuation>,</span> <span class=type>B</span>=<span class=type>B</span><span class=punctuation>>></span> <span class=punctuation>{</span>
    <span class=type>Box</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>((</span>s<span class=punctuation>,</span> <span class=constructor>PhantomData</span><span class=punctuation>))</span>
<span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>use_box</span><span class=punctuation>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>DoubleAssociated</span><span class=punctuation>>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
    <span class=type>DoubleAssociated</span><span class=punctuation>::</span><span class=function>as_ref</span><span class=punctuation>(</span><span class=type>Box</span><span class=punctuation>::</span><span class=function>as_ref</span><span class=punctuation>(</span>s<span class=punctuation>)).</span><span class=function>name</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>Whuh oh</p><pre class=syntax-highlighting><code>error[E0191]: the value of the associated types `S` and `B` in `DoubleAssociated` must be specified
  --> src/main.rs:44:24
   |
14 |     type S: Generic&lt;Self::B>;
   |     ------------------------ `S` defined here
15 |     type B;
   |     ------ `B` defined here
...
44 | fn use_box(s: &Box&lt;dyn DoubleAssociated>) -> &'static str {
   |                        ^^^^^^^^^^^^^^^^
   |
help: specify the associated types
   |
44 | fn use_box(s: &Box&lt;dyn DoubleAssociated&lt;S = /* Type */, B = /* Type */>>) -> &'static str {
   |                                        ++++++++++++++++++++++++++++++++
</code></pre><p>It wants us to use <span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi mathvariant=normal>∀</mi></mrow><annotation encoding=application/x-tex>\forall</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=height:.6944em></span><span class=mord>∀</span></span></span></span> <span></span> again... to be honest, I totally did not expect this. I thought the fact that Rust enforces "at most one concrete trait implementation per concrete type" meant associated types could be stored in the trait object vtable somehow, but my intuition was way wrong on that.</p><p>Thinking on it further, the fact that we return things type <code>Associated::S</code> means that, at runtime, different <code>DoubleAssociated</code> impls will necessarily have different vtables, because return types will have different layouts, so the trait objects with different associated types MUST be different. Rust 1 – Me 0</p><p>So, what can we take away from this? That it's impossible to fully automatically erase multiple existential bounds in Rust in the same type, because it only supports one at a time with <code>dyn Trait</code>? Unfortunately yes, that is my takeaway here :(</p><p>Still, if we're willing to put in a little bit more legwork, it is possible to manually collapse multiple existential bounds into one.</p><h2><a aria-hidden=true class=anchor href=#attempt-3-manual-erasing id=heading-attempt-3-manual-erasing></a>Attempt 3: Manual Erasing</h2><p>Forget trying to use functions from <code>Generic&lt;B></code> directly, let's instead create a trait that manually, for each function we care about, passes thru to the function from the trait:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>Erased</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>name</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span><span class=punctuation>;</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>></span> <span class=type>Erased</span> <span class=keyword>for</span> <span class=punctuation>(</span><span class=type>S</span><span class=punctuation>,</span> <span class=type>PhantomData</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>)</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>name</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
        <span class=variable>self</span><span class=punctuation>.</span><span class=constant>0</span><span class=punctuation>.</span><span class=function>name</span><span class=punctuation>()</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>This works!! We can even box it, hooray (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=67deae4b6f9b2145945b74b50418b89a">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>fn</span> <span class=function>mk_box</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> + <span class=operator>'</span><span class=label>static</span><span class=punctuation>>(</span><span class=variable>s</span><span class=punctuation>:</span> <span class=type>S</span><span class=punctuation>)</span> -> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Erased</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=type>Box</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>((</span>s<span class=punctuation>,</span> <span class=constructor>PhantomData</span><span class=punctuation>))</span>
<span class=punctuation>}</span>

<span class=keyword>fn</span> <span class=function>use_box</span><span class=punctuation>(</span><span class=variable>b</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Erased</span><span class=punctuation>>)</span> -> <span class=operator>&'</span><span class=label>static</span> <span class=type>str</span> <span class=punctuation>{</span>
    b<span class=punctuation>.</span><span class=function>name</span><span class=punctuation>()</span>
<span class=punctuation>}</span></code></pre><p>Alice has <a href=https://bsky.app/profile/welltypedwit.ch/post/3mlqu5urfjc2s>one last request for us here</a>:</p><blockquote><p>hmmm but now this one doesn't work if the trait actually uses the <code>B</code> parameter right? (because you can't use it in <code>Erased</code>)</p></blockquote><p>And that is true: <code>Erased</code> <em>intentionally</em> has no way for it to vary what types it cares about, everything needs to be concrete so it can be put inside a <code>dyn</code> without issues. If we want to deal with multiple types, we'll have to use <a href=https://doc.rust-lang.org/std/any/trait.Any.html><code>std::any::Any</code></a> to handle them at runtime. Here's an example (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=8552501df3bcf4ae4d80aef60d3073b8">playground link</a>):</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>use</span> std<span class=punctuation>::</span>any<span class=punctuation>::</span><span class=constructor>Any</span><span class=punctuation>;</span>
<span class=keyword>use</span> std<span class=punctuation>::</span>marker<span class=punctuation>::</span><span class=constructor>PhantomData</span><span class=punctuation>;</span>

<span class=keyword>trait</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>input</span><span class=punctuation>(</span><span class=variable>b</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>B</span><span class=punctuation>);</span>
    <span class=keyword>fn</span> <span class=function>output</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=type>B</span><span class=punctuation>;</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>f32</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>i32</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>input</span><span class=punctuation>(</span><span class=variable>b</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>f32</span><span class=punctuation>)</span> <span class=punctuation>{</span> <span class=macro>println!</span><span class=punctuation>(</span><span class=string>"f32: {b}"</span><span class=punctuation>)</span> <span class=punctuation>}</span>
    <span class=keyword>fn</span> <span class=function>output</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=type>f32</span> <span class=punctuation>{</span> <span class=operator>*</span><span class=variable>self</span> <span class=keyword>as</span> <span class=type>f32</span> <span class=punctuation>}</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>f64</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>i32</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>input</span><span class=punctuation>(</span><span class=variable>b</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>f64</span><span class=punctuation>)</span> <span class=punctuation>{</span> <span class=macro>println!</span><span class=punctuation>(</span><span class=string>"f64: {b}"</span><span class=punctuation>)</span> <span class=punctuation>}</span>
    <span class=keyword>fn</span> <span class=function>output</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=type>f64</span> <span class=punctuation>{</span> <span class=operator>*</span><span class=variable>self</span> <span class=keyword>as</span> <span class=type>f64</span> <span class=punctuation>}</span>
<span class=punctuation>}</span>

<span class=keyword>trait</span> <span class=type>Erased</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>input</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>b</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Any</span><span class=punctuation>>);</span>
    <span class=keyword>fn</span> <span class=function>output</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Any</span><span class=punctuation>>;</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>:</span> <span class=operator>'</span><span class=label>static</span><span class=punctuation>,</span> <span class=type>S</span><span class=punctuation>:</span> <span class=type>Generic</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>></span> <span class=type>Erased</span> <span class=keyword>for</span> <span class=punctuation>(</span><span class=type>S</span><span class=punctuation>,</span> <span class=type>PhantomData</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>>)</span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>input</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>b</span><span class=punctuation>:</span> <span class=operator>&</span><span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Any</span><span class=punctuation>>)</span> <span class=punctuation>{</span>
        <span class=type>S</span><span class=punctuation>::</span><span class=function>input</span><span class=punctuation>(</span>b<span class=punctuation>.</span><span class=function>downcast_ref</span><span class=punctuation>().</span><span class=function>unwrap</span><span class=punctuation>())</span>
    <span class=punctuation>}</span>
    <span class=keyword>fn</span> <span class=function>output</span><span class=punctuation>(</span><span class=operator>&</span><span class=variable>self</span><span class=punctuation>)</span> -> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=keyword>dyn</span> <span class=type>Any</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>Box</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>.</span><span class=constant>0</span><span class=punctuation>.</span><span class=function>output</span><span class=punctuation>())</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>This compiles & runs successfully! Unfortunately, as you can see, manual erasure has a few drawbacks besides just dyn-compatibility.</p><details><summary>Aside on dyn-compatibility</summary> <p>I've mentioned <code>dyn Trait</code> a few times in this article, but what sorts of traits can actually be put inside a <code>dyn</code>? There are a <a href=https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>whole bunch of rules</a> known as "dyn-compatibility" that boil down to "only do stuff that you can put inside a vtable". Most limiting is the fact that you <em>have</em> to take <code>self</code> as a parameter for every function, because that is the only way we're able to get at a value's vtable. This is why <code>Erased::input()</code> takes in <code>&self</code> while <code>Generic::input()</code> does not.</p></details><p>This process, as I've outlined above, is <em>very</em> manual: you have to rewrite every function signature to include <code>Box&lt;dyn Any></code> instead of <code>B</code>, and make a decision for each about whether to <code>.unwrap()</code> or propagate the error up somehow. If there are enough functions to do this conversion on, you <em>could</em> write a <code>macro_rules!</code> to define a <code>Generic&lt;B></code> and <code>Erased</code> trait at the same time, but that seems very finnicky and I have been nerd-sniped enough already so I will not attempt that here :P</p><p>Anyways that's it, thanks for reading, hope you learned a little something in the process!</p><section class=footnotes data-footnotes><ol><li id=fn-sized><p>Ignore for the moment that trait objects are <code>!Sized</code>, it is not relevant to our purposes right now :) <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-sized>↩</a></p></li><li id=fn-dyn><p>And the fact that it's actually represented by a trait object, of course. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-dyn>↩</a></p></li><li id=fn-nlab><p>Very sorry for inflicting nlab type theory upon u, it is a cognitohazard of the highest order, nothing in it makes sense until you stare very very hard and then suddenly your brain falls out and everything makes sense and you can start speaking the same gibberish. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-nlab>↩</a></p></li><li id=fn-specialization><p>At least, not until the <a href=https://rust-lang.github.io/rfcs/1210-impl-specialization.html>specialization RFC</a> lands, which it probably never will :') <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-specialization>↩</a></p></li><li id=fn-self-types><p>If we wanted to use functions from <code>Generic&lt;B></code> that accepted <code>self</code> (no borrow) or <code>&mut self</code>, we'd have to add more functions to <code>DoubleAssociated</code> for those too, but like <code>as_ref()</code>, they're also trivial. <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-self-types>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[This World Is Not Real (textless)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-29-this-world-is-not-real-textless/" />
      <id>https://wolfgirl.dev/blog/2026-04-29-this-world-is-not-real-textless/</id>
      <published>2026-04-29T21:50:24Z</published>
      <updated>2026-04-29T21:50:24Z</updated>
      <summary><![CDATA[i felt bad about inflicting psychic damage so i toned it down a little.]]></summary>
      <content type="html"><![CDATA[<p>Textless companion to <a href=/blog/2026-04-29-this-world-is-not-real/>This World Is Not Real</a>, so as to avoid the very very heavy text component and let the images stand on their own. Without the text I think the interpretation is not nearly as hazardous.</p><hr><p><br><br></p><p><img alt="various office building cubicles, as viewed from above & outside the building. slight imperfections in the glass make each yellow-LED-lit office seem warped, despite the perfectly straight grid of window borders." title="offices from above" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/01_IMG_0742-3.png></p><p><br><br> <br><br></p><p><img alt="an office building looks normal from the outside, but as soon as the glass rounds the corner & stop reflecting the exterior, you can see the interior glow a deep yellow." title="the interior beckons" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/02_IMG_0711-2.png></p><p><br><br> <br><br></p><p><img alt="looking up at a building emerging from the dark, only its lights visible, shining so bright they saturate all primary hues to form a brilliant white, causing wavy streaks in-line with the angle you're looking at it. it's like a spaceship, or a futuristic prison." src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/03_IMG_1496-2.png title=astigmatism></p><p><br><br> <br><br></p><p><img alt="three buildings loom in the night, each corrupted in their own way, standing over the light from the streets below & reaching up toward a light up above, both out-of-frame, crowded out by the full height of the buildings." title="exterior corruption" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/04_IMG_1490-2.png></p><p><br><br> <br><br></p><p><img alt="a view of an apartment building with fancy curved windows is broken into unequal quarters, each becoming progressively more blocky, as if your vision is becoming digitally corrupted." title="interior corruption" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/05_IMG_1499-2.png></p><p><br><br> <br><br></p><p><img alt="two buildings, colored impossibly pure red, red light leaking out their windows and green tint covering swaths like moss, flank a similarly-red construction site at the foot of a towering green-and-red-lined skyscraper, all its windows nearing pure black. The only lights visible are pure-black ones giving off a yellow halo, with a deep blue sky just barely visible beyond everything." src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/06_MG_4554-3.png title=hell></p><p><br><br> <br><br></p><p><img alt="on the left of a building, a fairly normal-looking cloud is reflected in its glass. on the shaded side, those same clouds are transformed into rainbow heatmaps. inside the building, some impossibly-glowing pipes are arranged in geometric patterns. everything besides the clouds is crunchy + distorted." title="doppler radar" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/07_MG_4586.png></p><p><br><br> <br><br></p><p><img alt="an obelisk-shaped building with square windows is viewed at an impossible angle, the sky and a concrete railing cutting similar triangles in its silhouette. the windows themselves are also impossibly-colored, looking like a film negative in parts, and almost-normal in others." title="iridescent squares" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/08_MG_4602.png></p><p><br><br> <br><br></p><p><img alt="different windowpanes on a building have been swapped with each other (accounting for perspective distortion) and other parts, like some pipes, metal grill, and a lone tree branch have been cut-and-pasted. everything that resulted from this transformation is intensely saturated, especially compared to the almost-black-and-white rest of scene." title="swap, dup" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/09_IMG_0712-1.png></p><p><br><br> <br><br></p><p><img alt="One small cyan-and-pink building looks up at another, much larger cyan-and-pink skyscraper, with the latter taking up the vast majority of the view. the colors on each form a gradient, and you can just barely see that there is a corner to the larger building." title="cyan & pink" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/10_IMG_9698-1.png></p><p><br><br></p><hr><p>wow that webp compression sure does a number huh. remember to click in on each to see the full resolution!!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[This World Is Not Real]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-29-this-world-is-not-real/" />
      <id>https://wolfgirl.dev/blog/2026-04-29-this-world-is-not-real/</id>
      <published>2026-04-29T17:04:08Z</published>
      <updated>2026-04-29T17:04:08Z</updated>
      <summary><![CDATA[Alternative Title: Retribution]]></summary>
      <content type="html"><![CDATA[<p>Trigger warning for depression. This text contains some of my worst thoughts so please don't read if you're at risk/otherwise aren't ready<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>. If you would like to look at the images without the associated text, see <a href=/blog/2026-04-29-this-world-is-not-real-textless/>This World Is Not Real (textless)</a> instead. You won't be missing out on anything :))</p><hr><p>For a long time, you've suspected this world is not real.</p><p><br><br></p><p><img alt="various office building cubicles, as viewed from above & outside the building. slight imperfections in the glass make each yellow-LED-lit office seem warped, despite the perfectly straight grid of window borders." title="offices from above" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/01_IMG_0742-3.png></p><p><br><br></p><p>Not in the "identical trenchcoat-wearing agents teleport you behind a green film filter" sense, or a "moving movie set captures your daily life" sense, or a "timeline split placed you in the wrong universe" sense, or even a "every so often the human race is wiped out, quickly cloned from scratch from a bunker under Yellowstone, then imprinted with memories all at the same time; it happened last Thursday" sense, but something even more visceral than all those combined.</p><p><br><br></p><p><img alt="an office building looks normal from the outside, but as soon as the glass rounds the corner & stop reflecting the exterior, you can see the interior glow a deep yellow." title="the interior beckons" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/02_IMG_0711-2.png></p><p><br><br></p><p>You wake up in your bed that's not real and put some not-real clothes on your not-real body then brush your teeth which aren't real before eating some breakfast which isn't all that filling because it's not real then you check the clock and you would be late for work if time was real but it's not and neither is the door and neither is the sidewalk and neither is the car that you're driving and neither are the other cars in traffic or the traffic lights or the asphalt or the sound of the air through the engine bouncing off concrete panels, vibrating through tiny gaps in a steel-rubber-glass window interface, funneling into your amplifying sound receptacles where a snail-shaped organ with delicate hairs does a mechanical frequency analysis and sends the results as electrical signals to the empty void inside you head: there's nothing there because none of that intricately-described process was real, it was all made up, just like the job you're going to, which has about the same impact as sitting around all day doing nothing, because your work isn't real, the economy isn't real, society isn't real, and you, certainly, without a shred that could possibly be doubted even if shreds & doubt were real, <em><strong>aren't real</strong></em>.</p><p><br><br></p><p><img alt="looking up at a building emerging from the dark, only its lights visible, shining so bright they saturate all primary hues to form a brilliant white, causing wavy streaks in-line with the angle you're looking at it. it's like a spaceship, or a futuristic prison." src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/03_IMG_1496-2.png title=astigmatism></p><p><br><br></p><pre class=syntax-highlighting><code>THERE IS ONLY SO
</code></pre><p><br><br></p><p><img alt="three buildings loom in the night, each corrupted in their own way, standing over the light from the streets below & reaching up toward a light up above, both out-of-frame, crowded out by the full height of the buildings." title="exterior corruption" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/04_IMG_1490-2.png></p><p><br><br></p><pre class=syntax-highlighting><code>MUCH YOU CAN TAKE BEFORE YOU
</code></pre><p><br><br></p><p><img alt="a view of an apartment building with fancy curved windows is broken into unequal quarters, each becoming progressively more blocky, as if your vision is becoming digitally corrupted." title="interior corruption" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/05_IMG_1499-2.png></p><p><br><br></p><pre class=syntax-highlighting><code>ARE FORCED TO ADMIT
</code></pre><p><br><br> <img alt="two buildings, colored impossibly pure red, red light leaking out their windows and green tint covering swaths like moss, flank a similarly-red construction site at the foot of a towering green-and-red-lined skyscraper, all its windows nearing pure black. The only lights visible are pure-black ones giving off a yellow halo, with a deep blue sky just barely visible beyond everything." src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/06_MG_4554-3.png title=hell></p><p><br><br></p><pre class=syntax-highlighting><code>NOTHING IN THIS WORLD
</code></pre><p><br><br></p><p><img alt="on the left of a building, a fairly normal-looking cloud is reflected in its glass. on the shaded side, those same clouds are transformed into rainbow heatmaps. inside the building, some impossibly-glowing pipes are arranged in geometric patterns. everything besides the clouds is crunchy + distorted." title="doppler radar" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/07_MG_4586.png></p><p><br><br></p><pre class=syntax-highlighting><code>IS REAL WAS REAL OR WILL BE
</code></pre><p><br><br></p><p><img alt="an obelisk-shaped building with square windows is viewed at an impossible angle, the sky and a concrete railing cutting similar triangles in its silhouette. the windows themselves are also impossibly-colored, looking like a film negative in parts, and almost-normal in others." title="iridescent squares" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/08_MG_4602.png></p><p><br><br></p><pre class=syntax-highlighting><code>REAL FOREVERMORE
</code></pre><p><br><br></p><p><img alt="different windowpanes on a building have been swapped with each other (accounting for perspective distortion) and other parts, like some pipes, metal grill, and a lone tree branch have been cut-and-pasted. everything that resulted from this transformation is intensely saturated, especially compared to the almost-black-and-white rest of scene." title="swap, dup" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/09_IMG_0712-1.png></p><p><br><br></p><p>You scream and it's not real and you cry and it's not real and the entire remaining gamut of human emotions aren't available to you right now, because they're not real, unreality seeping and dripping and crawling and squirming and pushing and forcing its way into every nook and cranny and crevice and inch and foot and mile of your brain and soul, dying it a deep void nothing that would erode your eyes if you tried to look at it but you can't since your eyes, just like the mirror and the you in the mirror and the you beyond the mirror, aren't real.</p><p><br><br></p><p><img alt="One small cyan-and-pink building looks up at another, much larger cyan-and-pink skyscraper, with the latter taking up the vast majority of the view. the colors on each form a gradient, and you can just barely see that there is a corner to the larger building." title="cyan & pink" src=https://static.wolfgirl.dev/polywolf/blog/019dd174-9431-7ff1-a633-5642e2c6c1a2/10_IMG_9698-1.png></p><p><br><br></p><p>Despite all of this, you must continue on in this world. Someday, you hope, with enough effort and just the right tricks, you can make all of it real. It will become real. <em>You shall be real</em>. All you have to do is—</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Also don't worry I am doing fine/much better now!! There were some rough spots but I got through them, mostly by hanging out with friends a lot more & acquiring a better job. I felt the need to finish this message anyways, to remind myself of just how far I've come since. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Better Clickable Images]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-27-better-clickable-images/" />
      <id>https://wolfgirl.dev/blog/2026-04-27-better-clickable-images/</id>
      <published>2026-04-28T00:07:22Z</published>
      <updated>2026-04-28T00:07:22Z</updated>
      <summary><![CDATA[Just a small update of a thing I'm doing to make image-heavy posts on this site nicer :)]]></summary>
      <content type="html"><![CDATA[<p>In preparation for releasing a new <a href=/tags/photography/>#photography</a> post, I've been messing with my blog engine to make images a bit better. Basically, the experience of "oh I want to zoom in on an image" is a bit busted, because (1) that only really works on mobile (desktop page zoom is always hard), and (2) the image you'd zoom in on is intentionally heavily compressed.</p><p>In my previous site, I solved this by writing a <a href=https://github.com/p0lyw0lf/website/blob/12cb31c11d47cc7eb89fcd890266df9b59b49745/src/plugins/rehypeRemoteImages.ts>rehype pass</a> that would wrap all to-be-converted images in <code>&lt;a>&lt;/a></code> tags pointing to the original image, but this got a little annoying because when you'd click on the image, the browser would immediately attempt to download it, and then you'd have to open an image viewer, remember to delete the image later, etc. What <em>should</em> happen is, if you click the image in the browser, you stay in the browser but just transition to a view of the original image at larger scale.</p><p>In fact, that's already what I do for my <a href=/art/>art page /art/</a>. This uses the <a href=https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/popover>HTML <code>popover</code> attribute</a> & <a href=https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog>HTML <code>&lt;dialog></code> element</a> to allow page interactivity without requiring a single line of Javascript!! All we need to do now is, whenever detecting an <code>![]()</code> markdown image, replace it with the appropriate HTML & CSS styles to get it to work. So that's what I did!</p><p>This wouldn't have been nearly as possible w/o my new blog engine btw, because the way Astro does automatic image stuff would have required me to make a lot of nasty hacks, but now those "hacks" are just part of the site itself, so I can do whatever I want with it!!</p><p>Overall I am much happier w/ the image-viewing experience now, I hope ppl make great use of it to zoom into images when I release my next set :3 (btw you should look at my <a href=/blog/2025-09-26-coho-photo-zine-yes-and-ism/>previous set</a> again too I think it is very good still)</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Hima-Ten: What Camera Do Tenichi & Honoka Use?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-27-what-camera-do-tenichi-honoka-use-in-hima-ten-/" />
      <id>https://wolfgirl.dev/blog/2026-04-27-what-camera-do-tenichi-honoka-use-in-hima-ten-/</id>
      <published>2026-04-27T21:49:20Z</published>
      <updated>2026-04-27T21:49:20Z</updated>
      <summary><![CDATA[In the (at time of writing) latest english chapter of the romance manga Hima-Ten, the main character Tenichi Iemori goes on a walking-aro...]]></summary>
      <content type="html"><![CDATA[<p>In the (at time of writing) <a href="https://www.viz.com/shonenjump/hima-ten-chapter-88/chapter/49865?action=read">latest english chapter of the romance manga Hima-Ten</a>, the main character Tenichi Iemori goes on a walking-around date with his childhood crush Honoka Kanai. Honoka brings along her parents' DSLR and Tenichi uses that as a lens (heh) to think about how he views Honoka as a romantic partner, because recently he's realized feelings for another heroine, Himari Yoshino.</p><p>Anyways the plot is not what we're here to talk about today I want to know what model of camera they used!!!!!</p><p>pg04 <img alt='Honoka smiles while holding a camera, saying "...I borrowed my parents&#39; camera!"' title="This thing is much too large for her lmaooo" src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3135.jpeg></p><p>From this first close-up, we can immediately tell it's a Canon, despite the lack of an explicit branding mark. The giveaways are the shape of the boot above the lens & the hand grip, both of which are fairly unique to Canon.</p><p>Upon seeing this camera, Tenichi says "An SLR?!". Given this information, we might be tempted to starting looking at just Canon Film SLRs. However, I think that's just what normal people call non-pocket-sized cameras, so for now we must include DSLRs and mirrorless in our search going forward.</p><p>However, even looking at every camera Canon's ever made, none match this body profile exactly. The combination of boot shape, distance from boot to lens mount, and position of the top dial are all things that can be found in isolation, but never perfectly together. Still, going thru everything gives us a few more hints:</p><ol><li>It is not a film camera, because those generally have wildly different (blockier) body shapes until the first EOS models, and no EOS film has that same top dial (presumably a mode-switch dial if it's digital).</li><li>It is not a mirrorless camera, because those never have that area between the lens and the boot (because they don't need to do anything w/ optics for their digital viewfinders).</li></ol><p>Therefore, despite the inexact dimensions, hitting all the key details means we can be relatively confident it is a Canon DSLR. Just from this one image!! Hopefully further closeups & better references will let us narrow it down further:</p><div class=table-wrapper><table><thead><tr><th>pg09</th><th>pg10</th></tr></thead><tbody><tr><td><img alt='Tenichi asks, "... You sure?" and Honoka responds, "Yeah." while Tenichi holds the camera, presumably talking about whether he can use it.' src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3136.jpeg></td><td><img alt='Tenichi internally exclaims, "This is it!" while gasping at the camera, because he&#39;s got a good shot' src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3137.jpeg></td></tr><tr><td>pg16</td><td></td></tr><tr><td><img alt="Honoka smiles, &#34;...That's the best one I've taken!&#34;, showing Tenichi a picture she took of him" src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3138.jpeg></td><td></td></tr></tbody></table></div><p>Fortunately, for the rest of the chapter, the mangaka realizes trying to draw a camera by hand is really hard/overly time-consuming, so they photobash it instead. This gives us a few more good clues into what the camera actually is.</p><p>The last image 100% confirms it's a DSLR; the image preview rules out it being film (lol) and the viewfinder focus correction dial rules out it being a mirrorless. Also, the button layout + the clearer body shape/badge location in the former images are a strong indicator this is a camera in the Rebel T-series, which I because I own very similar-looking T5i. Comparing the button layout to what I have, however, we see there's an extra "DISP" button in the main cluster on Honoka's camera, as well as it missing the "MENU" & "INFO" buttons on the top left bridge (photo from <a href=https://mpb.com>https://mpb.com</a>):</p><p><img alt="Rear panel of the T5i" src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3142.jpeg></p><p>Looking at all the other T-series, those ending in "i" are also missing this button, probably because it turns on/off the screen, which you can do on those "i" cameras by just flipping the screen around. Looking at the standard T5, T6, and T7, we see that the rubber patch is different compared to the T5, and exactly the same on the T6 & T7, so we can be fairly sure it's one of those (photos from <a href=https://mpb.com>https://mpb.com</a>).</p><div class=table-wrapper><table><thead><tr><th>T5</th><th>T6</th><th>T7</th></tr></thead><tbody><tr><td><img alt="Rear panel of the T5" src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3141.jpeg></td><td><img alt="Rear panel of the T6" src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3143.jpeg></td><td><img alt="Rear panel of the T7" src=https://static.wolfgirl.dev/polywolf/blog/019dcfe4-101c-777c-8ad1-d85ecf970ed8/IMG_3144.jpeg></td></tr></tbody></table></div><p>Just for good measure, I did in fact look at a bunch of other Canon DSLRs with similar body shapes, and indeed the T6/T7 are the only ones that match the back exactly. Unfortunately, this is where the trail runs cold. There are really are <em>no</em> external differences remaining between the T6 & T7; the <em>only</em> possible thing is the color of the zoom button labels, but we can't tell that from the manga, and everything else is better image sensor/software differences. So, without further evidence we cannot decide which one they used.</p><p>Fortunately, I <em>do</em> have some evidence to show, so bear with me :) On Canon's official website, we only see them (re-?)selling the <a href="https://www.usa.canon.com/shop/p/eos-rebel-t7-ef-s-18-55mm-f-3-5-5-6-is-ii?color=Black&type=New">EOS Rebel T7</a>, <em>not</em> the T6. I think this is because it's the last entry-level DSLR in their lineup, their last one before their move to mirrorless with the EOS R100 for that market segment. There was a 5-year gap between the T7's 2017 release and the R100's 2023 release, and the T7 is still being sold officially today. Meanwhile, there was only a 2-year gap between the T6's release in 2016 and the T7's release, and the T6 was discontinued immediately after the T7 started being sold. So, just speaking statistically, this camera is much more likely to be a T7, based on when the manga is set (effectively modern-day).</p><p>Some final notes:</p><ul><li>The pictures on Canon's official website don't exactly match the photobashed ones, but I don't take that as an issue, the photobashing looks pretty thoro anyways.</li><li>The lens also matches the 18-55mm EF-S zoom lens that comes with the kit, with it appearing "longer" in the pg10 picture perhaps due to it being in a zoom configuration.</li><li>While I have been using the American market names this entire time, the Japanese name for this camera is <a href=https://personal.canon.jp/product/camera/eos/kissx90>EOS Kiss x90</a>.</li></ul><p>With all that out of the way, I think I can pretty confidently say: <strong>Tenichi & Honoka use a <em>Canon EOS Kiss x90</em></strong>. Very appropriate for a romance manga lol!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Other Static Site Generators]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-14-other-static-site-generators/" />
      <id>https://wolfgirl.dev/blog/2026-04-14-other-static-site-generators/</id>
      <published>2026-04-14T15:16:07Z</published>
      <updated>2026-04-14T15:16:07Z</updated>
      <summary><![CDATA[This sat in draft for way too long whoops To celebrate a "working site" milestone in my driver SSG project, I decided to take a look back...]]></summary>
      <content type="html"><![CDATA[<p><del>This sat in draft for way too long whoops</del></p><p>To celebrate a "working site" milestone in my <a href=https://git.sr.ht/~polywolf/driver>driver</a> SSG project, I decided to take a look back at my <a href=https://wolfgirl.dev/blog/2026-02-23-so-ive-been-thinking-about-static-site-generators/>Static Site Generators post</a>. It got a lot of traffic at the time!! Thank you again to everyone who read & commented on it, y'all made my week &lt;3</p><p>As one might have expected, talking about SSGs online had a "360 noscope nerdsnipe headshot" effect, so across multiple platforms I have amassed a grand collection of Static Site Generators, based on peoples' mentions/recommendations, to gaze upon at our leisure. Sure it's not as grand a collection as <a href=https://myles.github.io/awesome-static-generators/>Awesome Static Generators</a> ("all 148 of them!") or <a href=https://jamstack.org/generators/>Jamstack</a> ("all 377 of them!") or <a href=https://staticsitegenerators.net/>staticsitegenerators.net</a> ("all 484 of them!"), but I think it's impressive nonetheless.</p><p>To avoid bias, I shall order them alphabetically, and give reviews to all:</p><ul><li><a href=https://www.11ty.dev/>11ty</a>: Embroiled in controversy over their acquisition by <a href=https://blog.fontawesome.com/eleventy-joins-font-awesome/>Font Awesome</a> & their recent rename to "Build Awesome", but beloved by a strong community. One of the classic Javascript SSGs.</li><li><a href=https://github.com/aslilac/bai>bai</a> by <a href=https://mckayla.blog/>Kayla</a>: Seems like a custom templating program, written in Rust, which can be used as part of an SSG? neat</li><li><a href=https://metacpan.org/pod/Blio>Bilo</a> by <a href=https://domm.plix.at/>domm</a>: Perl! Poking around the codebase it's honestly pretty readable. Very much a "tool-for-one-person" deal tho</li><li><a href=https://github.com/bertmuthalaly/blogserver>blogserver</a> by <a href=www.somethingdoneright.net/about>Bert</a>: Instead of <em>just</em> producing files, this then loads produced files as part of a Go binary, and serves directly from loaded memory that way. Looks like it uses a Make-like system called <a href=https://redo.readthedocs.io/en/latest/>redo</a> to efficiently produce changes, TIL about that looks cool.</li><li><a href=https://code.kat5.dev/nick/flora>flora</a> by <a href=https://codeandcake.dev/>Nick</a>: What a cute Ruby DSL! Be careful not to mix it up with <a href=https://flower.jyn.dev/>flower</a> by Jynn, that one uses a Clojure DSL instead.</li><li><a href=https://git.esy.fun/yogsototh/her.esy.fun>her.esy.fun</a> by <a href=https://her.esy.fun/about-me.html>Yann</a>: Makefile + org-mode, a classic pairing</li><li><a href=https://ikiwiki.info/>ikiwiki</a> by <a href=https://joeyh.name/>Joey</a>: Is a wiki a blog? I guess it sort of is, considering <a href=https://wiki.alopex.li/>icefox's blog</a> is a wiki. Anyways I like the name, it is a bit of complex Perl however</li><li><a href=https://github.com/jaredkrinke/luasmith>luasmith</a> by <a href=https://log.schemescape.com/>Jared</a>: I was thinking about using a Lua DSL for my project, but didn't because I like Javascript more. Pretty cool to see just how much can be done with just Lua, and also how many libraries there are for it!</li><li><a href=https://github.com/mk12/blog>mk12/blog</a> by <a href=https://mitchellkember.com/>mk12</a>: Zig, designed for speed, includes a custom Markdown & MathML parser?? pretty awesome honestly</li><li><a href=https://jaredkrinke.github.io/md2blog/>md2blog</a> by Jared (same person as before): Deno binary, "zero config" opinionated setup, another classic type of personal project. This came before luasmith.</li><li><a href=https://nanoc.app/>Nanoc</a> by <a href=https://denisdefreyne.com/>Denis</a>: Ruby! Tho this one uses more standard HTML templates. Seems like good docs too.</li><li><a href=https://github.com/winks/nextgen>nextgen</a> by <a href=https://f5n.org/about/>Florian</a>: Purpose-built generator where most all the logic is written in Rust, again highly-opinionated to site layout/features. If Javascript SSGs are classic then Rust SSGs are modern :3</li><li><a href=https://riki.liw.fi/>riki</a> by <a href=https://liw.fi/>Lars</a>: An attempt at re-writing ikiwiki in Rust. I like Rust more than Perl so I want this to succeed...!!</li><li><a href=https://github.com/susam/susam.net>susam.net</a> by <a href=https://susam.net/about.html>Susam</a>: Common Lisp!! Specifically, Steel Bank. Seems like this author has re-written their static site generation multiple times, where this is the latest. I tried to read it but it sure is Lisp yep.</li><li><a href=https://github.com/john-bokma/tumblelog>tumblelog</a> by <a href=https://johnbokma.com/>John</a>: Both Python & Perl versions of the same program, kept in feature parity?? They say it hasn't been done before now. Interesting concept to use Markdown files as the basis for templating instead of HTML; I guess makes sense given technically it's a subset, and given the intended use of the program (microblogging).</li><li><a href=https://yocaml.github.io/tutorial/>YOcaml</a>: It's always nice to see OCaml being used for real code &lt;3 Seems like a pretty extensive tutorial too!</li><li><a href=https://zensical.org/>Zensical</a>: An offshoot project created as part of the whole <a href=https://fpgmaas.com/blog/collapse-of-mkdocs/>MkDocs fiasco</a>. Was plugged to me in a comment on Mastodon, seems very corporate somehow.</li><li><a href=https://zine-ssg.io/>Zine</a> by <a href=https://kristoff.it/>Loris</a>: It's all Zig!! The configuration, frontmatter, & templates all being Zig-inspired is a neat touch :3</li></ul><p>I had a lot of fun cataloging these small corners of the internet. Really makes me aware of just how many other people are out there, also doing cool stuff. Hope this was as inspiring for you as it was for me!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[NEW BLOG ENGINE WORKING!!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-13-new-blog-engine-working-/" />
      <id>https://wolfgirl.dev/blog/2026-04-13-new-blog-engine-working-/</id>
      <published>2026-04-13T13:32:36Z</published>
      <updated>2026-04-13T13:32:36Z</updated>
      <summary><![CDATA[Finally!!! It's been a while since I started thinking about this, but I finally have things in a state where I can build my entire site u...]]></summary>
      <content type="html"><![CDATA[<p>Finally!!! It's been a while since I <a href=https://wolfgirl.dev/blog/2026-02-23-so-ive-been-thinking-about-static-site-generators/>started thinking about this</a>, but I finally have things in a state where I can build my entire site using it. In fact, you're looking at its output right now :3<sup class=footnote-ref><a data-footnote-ref href=#fn-rss id=fnref-rss>1</a></sup> The best part? <strong>Rebuilds for a single blog post changing content are 100ms baby</strong> lesgooooooo<sup class=footnote-ref><a data-footnote-ref href=#fn-improvements id=fnref-improvements>2</a></sup>.</p><p>Other types of rebuilds that change logic affecting all pages range from 200ms to 1sec, which is totally fine in my book. Overall I'm very pleased with the speed, which is good, considering that's literally the reason I started this whole project in the first place.</p><p>In the process of getting things this fast, I had to think a lot about algorithms & data structures, which also forced me to learn a lot of new things. I have a few <a href=https://rc.wolfgirl.dev/drafts/>draft posts</a> about that in the works, which, now that I've properly finished the coding, I will have no excuse but to work on instead. Keep an eye out for those I guess ¯\_(ツ)_/¯</p><section class=footnotes data-footnotes><ol><li id=fn-rss><p>You may have already noticed if you're using RSS; the feed went out briefly and has come back as an Atom feed instead. EDIT: I realized the syntax highlighting was broken too; that should be fixed now, with a custom (very bad) colorscheme I made myself. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-rss>↩</a></p></li><li id=fn-improvements><p>And that's before some other improvements I have in mind, like "don't spawn an absurd amount of Boa contexts" and "use a file watcher instead of hashing every input file every time". <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-improvements>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Running Man (2026 film)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-04-05-running-man-2026-film-/" />
      <id>https://wolfgirl.dev/blog/2026-04-05-running-man-2026-film-/</id>
      <published>2026-04-06T02:27:06Z</published>
      <updated>2026-04-06T02:27:06Z</updated>
      <summary><![CDATA[Just watched it on the plane. It's pretty dumb action schlock and the political messages are very heavy-handed & not at all handled well....]]></summary>
      <content type="html"><![CDATA[<p>Just watched it on the plane. It's pretty dumb action schlock and the political messages are very heavy-handed & not at all handled well. However. "We need an armed revolution that kills billionaires" is pretty funny ngl</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[So I've Been Thinking About Static Site Generators]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-02-23-so-ive-been-thinking-about-static-site-generators/" />
      <id>https://wolfgirl.dev/blog/2026-02-23-so-ive-been-thinking-about-static-site-generators/</id>
      <published>2026-02-23T14:49:43Z</published>
      <updated>2026-02-23T14:49:43Z</updated>
      <summary><![CDATA[My current one is _fine_, but the rebuilds are a bit too long for my tastes (>10s) so I'd like to change that.]]></summary>
      <content type="html"><![CDATA[<p>I'm aware of 2 broad classes of SSGs: those written for the authors' personal use which are quirky in interesting ways (a <a href=https://maurycyz.com/misc/new_ssg/>very</a> <a href=https://gearsco.de/blog/blog-in-gleam/>common</a> <a href=https://aashvik.com/posts/shell-ssg/>topic</a> <a href=https://sukr.io/>on</a> lobste.rs), and ones written for a mass audience that are more mellow but flawed in some other way (<a href=https://jekyllrb.com/>Jekyll</a>, <a href=https://gohugo.io/>Hugo</a>, <a href=https://jaspervdj.be/hakyll/>Hakyll</a>, <a href=https://www.getzola.org/>Zola</a>, <a href=https://astro.build/>Astro</a>, & many others). I've dealt with the latter kind for as long as I've had a blog, so it's about time I took a crack at the former.</p><p>This post will go over what I want, which afaik nothing on the market satisfies, and what I plan on doing about that.</p><h2><a aria-hidden=true class=anchor href=#i-have-a-need-for-speed️ id=heading-i-have-a-need-for-speed️></a>I Have A Need... For Speed™️</h2><p>My top priorities in a static site generator are:</p><ol><li>I can port my current site to it without too much difficulty</li><li>It is very very fast, "🚀BLAZING🔥" even</li></ol><p>But what exactly does "fast" mean? Many times have I been saddened by Static Site Generators not being fast in certain ways, so learning from my experiences I'd like it to be fast in <strong>all possible ways</strong> please & thank you. Notably, we don't need a <a href=https://jade.fyi/blog/the-postmodern-build-system/>full-fledged build system</a> for this, so we'll cut corners wherever possible to focus on speed for the SSG niche.</p><h2><a aria-hidden=true class=anchor href=#1-i-want-fast-clean-builds id=heading-1-i-want-fast-clean-builds></a>(1) I Want Fast Clean Builds</h2><p>I'm not <em>too</em> concerned about these, because I would rather satisfy the upcoming requirements around re-builds, which are the majority of one's interaction with an SSG. 'd still be nice to be under a second tho.</p><p>Testing my current site, the most expensive part seems to be the in-memory transformations for all the separate files; surprisingly, file I/O itself is not the bottleneck! Here's an <a href=https://www.man7.org/linux/man-pages/man1/strace.1.html>strace</a>:</p><pre class=syntax-highlighting><code>$ strace -c node node_modules/astro/astro.js build
...
18:34:57 [build] 301 page(s) built in 12.34s
18:34:57 [build] Complete!
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ------------------
 23.41    0.091898           3     30254       472 futex
 12.57    0.049332           1     39546     14563 statx
  8.74    0.034312          25      1352           unlink
  6.76    0.026544           1     15055           read
  6.43    0.025225           0     28933     27981 readlink
  6.09    0.023891           1     12906      5956 openat
  4.63    0.018191           4      3955           write
  4.56    0.017918           2      6894           epoll_pwait
  4.19    0.016459           2      7551           close
  2.50    0.009800        2450         4           wait4
  2.31    0.009070           8      1045           io_uring_enter
  1.78    0.006971           7       953           mmap
  1.67    0.006570           6       969           munmap
  1.57    0.006181           3      1694           mprotect
  1.41    0.005520        1840         3           clone
  1.33    0.005221           0      5781      4622 access
  1.26    0.004962           4      1055           madvise
  1.24    0.004865           0      7471           getpid
  1.16    0.004536           7       609       301 rmdir
  1.14    0.004478           0      6084           setsockopt
  1.11    0.004349          13       327           brk
  1.08    0.004240           4      1029           fstat
...
</code></pre><p>The total reported build time is 12.34s, yet syscalls are only *does some quick math* 0.4s of that total! (Also, that number of syscalls seems totally unreasonable, but whatever.) My guess is having <em>everything</em> in Javascript accumulates performance woes from all the data shuffling, and there's not a whole lot to be done except change frameworks/languages.</p><h3><a aria-hidden=true class=anchor href=#-build-must-be-in-a-compiled-language id=heading--build-must-be-in-a-compiled-language></a>=> Build MUST Be In A Compiled Language</h3><p>This really is the only way we can make content transformations fast enough. Fortunately, there is a wealth of non-Javascript-based Markdown-To-HTML & HTML-To-Minified-HTML programs to choose from; <a href=https://github.com/kivikakk/comrak>comrak</a> & <a href=https://github.com/wilsonzlin/minify-html>minify-html</a> both look pretty good.</p><p>However... just because the "expensive" steps are in compiled languages, doesn't mean the build as a whole will be fast. Maybe I was holding it wrong, but trying a <a href=https://github.com/p0lyw0lf/website2/blob/74799c1253ad52f4e713169c6c35248931cadc1a/Makefile>Makefile-based solution</a> led to so many reads/process spawns that a clean build took about 5 seconds, already much too slow for my tastes. <code>strace</code> doesn't work quite as well here (besides saying "yes, waiting for subprocesses really is the blocker"), so I guess the cumulative overhead of running a bunch of processes that all touch a bunch of files (~600 of both) is just too much, somehow.</p><h3><a aria-hidden=true class=anchor href=#-build-should-be-a-single-process id=heading--build-should-be-a-single-process></a>=> Build SHOULD Be A Single Process</h3><p>Ideally, there's just one compiled program running the entire build start-to-finish, reading all the files into memory (my site is not very large), transforming them in-memory, and writing back exactly what's changed. This limits file I/O to the bare minimum, and we should be able to make the in-memory transforms fast too. Existing known-fast SSGs do this, so we must be on the right track!</p><h2><a aria-hidden=true class=anchor href=#2-i-want-fast-re-builds-when-content-changes id=heading-2-i-want-fast-re-builds-when-content-changes></a>(2) I Want Fast Re-Builds When Content Changes</h2><p>I <em>could</em> just say "Fast Re-Builds are just Fast Clean Builds" and be done with it pack it up go home; that's what Hugo, Zola, & most homebrew SSGs do. But no, it'd be boring doing the same thing as everyone else. We need super specil[sic] fast. We need something with a cache, a cache <em>so</em> robust & complete that a re-build runs <em>exactly</em> what's needed based on what's changed, and no more. This is known in the business as an "incremental build". My modest goal is sub-300ms rebuild times, ideally sub-100ms or even sub-20ms if I can swing it.</p><p>However! Turns out doing proper incremental builds is kinda tricky. I guess is this why most frameworks avoid it :P Even if you can swing complete dependency tracking, a naïve method of "this file is dirty so I should rebuild everything that depends on it" doesn't suffice, because that can and will over-rebuild. Consider the following scenario:</p><ul><li>Pages are set up such that each one has a link to the next and previous pages, if they exist.</li><li>Markdown files are stored with <a href=https://www.markdownlang.com/advanced/frontmatter.html>frontmatter</a> specifying the title that should be used for any links pointing to them.</li></ul><p>Then, the rebuild would go like this:</p><ol><li>Markdown file changed, marked dirty</li><li>=> Frontmatter metadata is marked dirty</li><li>=> Collection of <em>all</em> metadata is marked dirty</li><li>=> Any page reading from "collection of all metadata" is marked dirty</li><li>All pages read from "collection of all metadata" to create next/previous links</li><li>=> All pages are marked dirty</li></ol><p>Notably, step (2) happens even if only the page content changes! This means <em>any</em> change to <em>any</em> page causes <strong>the entire set of pages</strong> to get re-built, negating any benefits of caching.</p><p>You can somewhat get around this by breaking step (3), precisely tracking which pages read from what metadata so a more minimal set of pages are rebuilt, but an even better approach is to also break step (2), by <strong>only marking the frontmatter metadata as dirty if it actually changes.</strong></p><h3><a aria-hidden=true class=anchor href=#-build-must-use-a-real-incremental-algorithm id=heading--build-must-use-a-real-incremental-algorithm></a>=> Build MUST Use A Real Incremental Algorithm</h3><p>There are a couple approaches to this sort of fine-grained incrementality that I'm aware of:</p><ol><li>The Red-Green Algorithm <a href=https://salsa-rs.github.io/salsa/reference/algorithm.html#the-red-green-algorithm>as used by Rust for incremental compilation</a> & <a href=https://thunderseethe.dev/posts/lsp-base/#red-green-algorithm>explained really well by thunderseethe</a></li><li>Nix's derivation model, <a href=https://shuppy.org/posts/10000251.html>explained pretty well by shuppy</a></li><li>Jade's <a href=https://jade.fyi/blog/the-postmodern-build-system/>postmodern build system</a>, which also covers the first two</li><li><a href=https://www.microsoft.com/en-us/research/wp-content/uploads/2018/03/build-systems.pdf>Build Systems à la Carte</a>, which provides a framework for all possible other approaches</li></ol><p>I am a huge Rust-head so let's go with the first approach :3</p><p>How Red-Green deals with our example is, a step can be marked "green" (unchanged) even if it has "red" (changed) dependencies, so long as its output doesn't change on the re-run. This does the correct thing if both the metadata <em>hasn't</em> changed (only (1) is dirty, (2) is not), as well as if it <em>has</em> changed (1-3 are truly dirty, but many things at 4 aren't dirty because they read the unchanged parts).</p><details><summary>Here's a longer trace I wrote to convince myself this works</summary> <p>In our example, there are a few "queries" which are individually cached<sup class=footnote-ref><a data-footnote-ref href=#fn-queries id=fnref-queries>1</a></sup>:</p> <pre class=syntax-highlighting><code>fn parse_frontmatter(f: File) -> Metadata;
fn collect_metadata(fs: Vec[File]) -> CollectedMetadata;
fn index_metadata(cm: CollectedMetadata, i: usize) -> Option[Metadata];
fn build_page(f: File, cm: CollectedMetadata, i: usize) -> ();
fn build_all_pages(fs: Vec[File]) -> ();
</code></pre> <p>These queries form a dependency graph as follows:</p> <pre class=syntax-highlighting><code>build_all_pages() >---------------------\
       v                                |
       |                                |
       v                                |
collect_metadata()                      |
       v                                |
       |                                |
+------+--+---------+---------+         |
|         |         |         |         |
v         v         v         v         |
parse(0)  parse(1)  parse(2)  parse(3)  |
                                        |
index(0)  index(1)  index(2)  index(3)  |
^         ^         ^         ^         |
|         |         |         |         |
+--------/^\--------+--------/^         |
^\--------+--------/^\--------+         |
|         |         |         |         |
^         ^         ^         ^         |
build(0)  build(1)  build(2)  build(3)  |
^         ^         ^         ^         |
|         |         |         |         |
+---------+---------+---------+---------/
</code></pre> <p>Or, a bit more readably:</p> <ul><li><code>build_all_pages</code> depends on <code>collect_metadata</code> and all the individual <code>build</code> calls.</li><li>each <code>build</code> call depends on its own <code>index</code>, as well as the adjacent <code>index</code> calls.</li><li>each <code>index</code> call depends on "nothing", since it is pure wrt it's inputs. It still needs to be separate from the <code>parse</code> calls though, just in case file order changes during the collection step.</li><li>the <code>collect_metadata</code> call depends on each <code>parse</code> call.</li></ul> <p>Then, say we change the metadata for the first file. The algorithm works like[^rg-terminology]:</p> <ul><li><code>build_all_pages</code>: Have my dependencies changes? <ul><li><code>collect_metadata</code>: Have my dependencies changed? <ul><li><code>parse(0)</code>: Changed!</li></ul></li><li><code>collect_metadata</code>: Changed! Time to re-compute. <ul><li><code>parse(0)</code>: Already marked as changed</li><li><code>parse(1)</code>: Not changed</li><li><code>parse(2)</code>: Not changed</li><li><code>parse(3)</code>: Not changed</li></ul></li></ul></li><li><code>build_all_pages</code>: Changed! Time to re-compute. <ul><li><code>build(0)</code>: Have my dependencies changed? <ul><li><code>index(0)</code>: Changed!</li></ul></li><li><code>build(0)</code>: Changed! Time to re-compute. <ul><li><code>index(0)</code>: Already marked as changed.</li><li><code>index(1)</code>: Not changed</li></ul></li><li><code>build(1)</code>: Have my dependencies changed? <ul><li><code>index(0)</code>: Already marked as changed</li></ul></li><li><code>build(1)</code>: Changed! Time to re-compute <ul><li><code>index(0)</code>: Already marked as changed</li><li><code>index(1)</code>: Already marked as not changed</li><li><code>index(2)</code>: Not changed</li></ul></li><li><code>build(2)</code>: Have my dependencies changed? <ul><li><code>index(1)</code>: Already marked as not changed</li><li><code>index(2)</code>: Already marked as not changed</li><li><code>index(3)</code>: Not changed</li></ul></li><li><code>build(2)</code>: Not changed, no need to re-compute.</li><li>(<code>build(3)</code> is mostly the same as <code>build(2)</code>)</li></ul></li></ul> <p>As expected, we only needed to re-compute <code>build(0)</code> & <code>build(1)</code>. Neat!</p></details><p>All of this is also why we need hermetic (deterministic) builds. Red-Green & other incremental algorithms assume that "inputs didn't change" => "outputs won't change either". In the context of a general build system running external programs, this assumption may be incorrect or the inputs very hard to specify<sup class=footnote-ref><a data-footnote-ref href=#fn-hermeticity-hard id=fnref-hermeticity-hard>2</a></sup>. But in the context of a Static Site Generator where you can tightly control all the build logic, I think it is a perfectly fine assumption to make<sup class=footnote-ref><a data-footnote-ref href=#fn-tracing-easy id=fnref-tracing-easy>3</a></sup>.</p><h2><a aria-hidden=true class=anchor href=#3-i-want-fast-re-builds-when-logic-changes id=heading-3-i-want-fast-re-builds-when-logic-changes></a>(3) I Want Fast Re-Builds When Logic Changes</h2><p>Even among other SSGs that do have caching (which the vast majority do not, mind you), they'll assume "most the time you'll be editing content, requiring a full re-build when logic changes is totally fine." But that assumption is bad, actually. Having quick iteration times when hacking on the logic of the site is super valuable & not something I will give up. It's fine if those builds a little slower than changing mere content, but it still shouldn't require a clean build of the entire site just to change the number of posts shown on the home page.</p><p>This might be tricky, however, because the previous two requirements <em>reeeeeeally</em> want the build to be in a compiled language. But we'll get there. Just u watch.</p><p>First, I'd like to draw a distinction between the "build driver" (program that reads/transforms/writes files) and the "logic" (specification for <strong>what</strong> files are read/written, and what transforms will be done).</p><p>These don't have to be the same program! In fact, many, nay, most SSGs separate these concepts. A common models is a "build driver" (Jekyll, Hugo, Zola) that finds & interprets a bunch of template files containing site-specific logic. These frameworks vary in how much logic they let you express with those templates/configuration files, but what they share in common is <strong>the build driver stays the same for all runs</strong>.</p><p>My one gripe with this model is, the faster the builds are, the less powerful the templates tend to be. <a href=https://flower.jyn.dev/#why-clojure?>As jyn mentions</a>, writing logic inside such templating languages sucks & feels bad, so it'd be much nicer if we were able to write logic in a "real" language instead.</p><p>This brings us to another form of static site generator: one that merely provides a framework of composable tools to describe how your site should be built, and then "building the site" is just running the program you've assembled. Hakyll is an archetype for this, and I'd describe "powerful" Javascript frameworks such as Next.JS or Astro as this too: they all have "real languages" to push data into templates, rather than relying solely on folder structure/config files.</p><p>Unfortunately, this increased flexibility comes with a cost. If all the tools are Javascript, we get performance woes from earlier. And the logic is written in a compiled language, you have to re-compile whenever the logic changes, and that re-compile can take a while. Here are some language options we could choose, ordered by how familiar I am with it:</p><div class=table-wrapper><table><thead><tr><th align=left>Language</th><th align=left>Rebuilds</th><th align=left>Why Not?</th></tr></thead><tbody><tr><td align=left>Rust</td><td align=left>Incremental</td><td align=left>Still too slow (2-5s)</td></tr><tr><td align=left>Go</td><td align=left>Incremental</td><td align=left>I don't like it, only a bit faster than Rust</td></tr><tr><td align=left>C/C++</td><td align=left>Incremental</td><td align=left>hahahaha no</td></tr><tr><td align=left>Haskell</td><td align=left>Incremental</td><td align=left><em>Way</em> too slow, even with incremental</td></tr><tr><td align=left>Swift</td><td align=left>Incremental</td><td align=left>Incremental is overly-conservative, too slow</td></tr><tr><td align=left>OCaml</td><td align=left>Incremental</td><td align=left>Slow in many ways :)</td></tr><tr><td align=left>Zig</td><td align=left>Not Incremental</td><td align=left>Waiting for stable incremental support</td></tr><tr><td align=left>JVM-based</td><td align=left>?</td><td align=left>I don't know any, nor have a desire to</td></tr></tbody></table></div><p>The problem is, even among languages with incremental rebuilds, they still aren't "🚀BLAZING🔥FAST" enough for what I want. It is very impressive they're able to go as fast as they are, but remember I'm targeting sub-100ms, not sub-1s. That order of magnitude matters.</p><p>So, we've eliminated "just use templates", and also "just use a compiled language". That means we're left with:</p><h3><a aria-hidden=true class=anchor href=#-logic-must-be-in-a-scripting-language id=heading--logic-must-be-in-a-scripting-language></a>=> Logic MUST Be In A Scripting Language</h3><p>This will probably result in the speed tradeoff I am most satisfied with. Probably. "Doing the incremental steps" & "transforming the files" are still best done in a compiled language, because those are a thorny bit of programming with plenty of opportunity to be slow. The remaining logic can use that incrementality and be written in a scripting language to support faster on-the-fly changes. Separating the "build driver" from the "logic" is what other fast SSGs do after all, we're just making each a bit more powerful ;)</p><h2><a aria-hidden=true class=anchor href=#my-vehicle-of-choice-rust-binary--javascript-interpreter id=heading-my-vehicle-of-choice-rust-binary--javascript-interpreter></a>My Vehicle Of Choice: <a href=https://git.sr.ht/~polywolf/driver>Rust Binary + Javascript Interpreter</a></h2><p>Based on everything above, I think you can see how we landed here: it's a hobby project, so I really really want to write Rust, and Javascript is a fine choice because:</p><ol><li>I know it well</li><li>The string templates are pretty good</li><li>It goes Fast Enough for logic</li><li>I'm very scared of homemade languages/Lisp</li></ol><p>Stuff that will go in the compiled Rust core:</p><ul><li>Incremental engine</li><li>Markdown parser (<a href=https://github.com/kivikakk/comrak>comrak</a>)</li><li>HTML minifier (<a href=https://github.com/wilsonzlin/minify-html>minify-html</a>)</li><li>URL Fetcher<sup class=footnote-ref><a data-footnote-ref href=#fn-fetching-urls id=fnref-fetching-urls>4</a></sup></li><li>Image Parser/Optimizer (TODO)</li><li>Javascript interpreter (<a href=https://github.com/delskayn/rquickjs>rquickjs</a>)</li></ul><p>And everything else, from base template partials to tag pages to the thing that makes a random PFP show up every time you refresh the page, will go in the Javascript files. It's another post entirely for how I'll actually structure the engine/scripts, so I'll leave it here for now :)</p><h2><a aria-hidden=true class=anchor href=#related-projects id=heading-related-projects></a>Related Projects</h2><p>Other incremental blog engines that inspired me to write this one:</p><ul><li>shuppy's cohost clone <a href=https://shuppy.org/posts/tagged/autost.html>autost</a></li><li>fasterthanlime's <a href=https://dodeca.bearcove.eu/>dodeca</a> project</li><li>jyn's <a href=https://flower.jyn.dev/>flower</a> project, especially their <a href=https://jyn.dev/four-posts-about-build-systems/>four posts about build systems</a></li></ul><p>I actually wanted to use one of these, but a hard look at each concluded they too fall under "for author's personal use only" and wouldn't suffice for my purposes. Still worth checking out tho!</p><p>Anyways that's it, stay tuned for if I ever finish this, hope you enjoyed ^^</p><section class=footnotes data-footnotes><ol><li id=fn-queries><p>A "real" set of queries might not look exactly like this (we don't necessarily want to pass down <code>CollectedMetadata</code> like that) but you get the overall idea. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-queries>↩</a></p></li><li id=fn-hermeticity-hard><p>jyn's excellent post goes into <a href=https://jyn.dev/build-system-tradeoffs/#hermetic-builds>what exactly makes hermetic builds/tracing builds hard</a>, once again I highly recommend reading their series on build systems <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-hermeticity-hard>↩</a></p></li><li id=fn-tracing-easy><p>My plan is, effectively, a "tracing build system with a cached action graph". To once again respond to <a href=https://jyn.dev/build-system-tradeoffs/#tracing>jyn's build system tradeoffs post</a>: (1) I avoid the complexities of tracing system calls because all "system calls" are just functions exposed in Javascript, (2) I avoid the downsides of serializing my action graph because my website is not web-scale, (3) I avoid the "the same file can come from many possible places" a.k.a negative dependency problem by simply not allowing negative dependencies. Reading this tradeoff post was very useful, because it allowed me to end up with quite a compact design by discarding all the things I don't care about! <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-tracing-easy>↩</a></p></li><li id=fn-fetching-urls><p>I specifically want this to copy Astro's support for <a href=https://docs.astro.build/en/guides/images/#remote-images>remote images</a>, which makes things like the <code>&lt;Image /></code> component & <code>inferRemoteSize</code> Just Work. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-fetching-urls>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Shoutouts To All The RSS Readers]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-02-05-shoutouts-to-all-the-rss-readers/" />
      <id>https://wolfgirl.dev/blog/2026-02-05-shoutouts-to-all-the-rss-readers/</id>
      <published>2026-02-06T04:08:58Z</published>
      <updated>2026-02-06T04:08:58Z</updated>
      <summary><![CDATA[I've been really busy and then really sick recently, so let's just do a quick one shall we? ;) User-Agents in alphabetical order, marked ...]]></summary>
      <content type="html"><![CDATA[<p>I've been really busy and then really sick recently, so let's just do a quick one shall we? ;)</p><p><code>User-Agent</code>s in alphabetical order, marked up with links where applicable:</p><ul><li><a href=https://github.com/skeeto/elfeed>Emacs Elfeed</a> 3.4.2<sup class=footnote-ref><a data-footnote-ref href=#fn-elfeed id=fnref-elfeed>1</a></sup></li><li><a href=https://feedbin.com/>Feedbin</a> feed-id:2970179 - 3 subscribers</li><li>FeedFlow (RSS Reader; +<a href=https://feedflow.dev>https://feedflow.dev</a>)</li><li>feedi/0.1.0 (+<a href=https://github.com/facundoolano/feedi>https://github.com/facundoolano/feedi</a>)</li><li><a href=https://feedly.com/news-reader>Feedly</a>/1.0 (+http://www.feedly.com/fetcher.html; 1 subscribers; )</li><li>feedparser/6.0.8 +<a href=https://github.com/kurtmckee/feedparser/>https://github.com/kurtmckee/feedparser/</a><sup class=footnote-ref><a data-footnote-ref href=#fn-feedparser id=fnref-feedparser>2</a></sup></li><li>FreshRSS/1.28.2 (Linux; <a href=https://freshrss.org>https://freshrss.org</a>)<sup class=footnote-ref><a data-footnote-ref href=#fn-freshrss id=fnref-freshrss>3</a></sup></li><li>Mozilla/5.0 (compatible; Miniflux/2.2.16; +<a href=https://miniflux.app>https://miniflux.app</a>)<sup class=footnote-ref><a data-footnote-ref href=#fn-miniflux id=fnref-miniflux>4</a></sup></li><li>Mozilla/5.0 (compatible; theoldreader; support@<a href=https://www.theoldreader.com/en/>theoldreader.com</a>; 1 subscribers; feed-id=40411548cd512d9254ed529)</li><li>NetNewsWire (RSS Reader; <a href=https://netnewswire.com/>https://netnewswire.com/</a>)<sup class=footnote-ref><a data-footnote-ref href=#fn-netnewswire id=fnref-netnewswire>5</a></sup></li><li><a href=https://newsboat.org/>Newsboat</a>/r2.42 (Linux x84_64)</li><li><a href=https://github.com/QuiteRSS/quiterss>QuiteRSS</a> 0.19.4 (EOL :c)</li><li><a href=https://reederapp.com/>Reeder</a>/5050102 CFNetwork/3860.300.31 Darwin/25.2.0</li><li><a href=https://github.com/rbren/rss-parser/>rss-parser</a></li><li>RSS.Social/1.0 +<a href=https://rss.social>https://rss.social</a>/bot</li><li>ScourRSSBot/1.0 (+<a href=https://scour.ing>https://scour.ing</a>/bot; 2 subscribers)</li><li><a href=https://sharnoff.io/>sharnoff.io</a>-rss-fetcher/5cb7472 (reqwest 0.12.7)<sup class=footnote-ref><a data-footnote-ref href=#fn-sharnoff id=fnref-sharnoff>6</a></sup></li><li>Skyreader/1.0 (+<a href=https://skyreader.app>https://skyreader.app</a>)</li><li><a href=https://github.com/spacecowboy/Feeder>SpaceCowboys Android RSS Reader</a> / 2.16.1(3858)<sup class=footnote-ref><a data-footnote-ref href=#fn-spacecowboys id=fnref-spacecowboys>7</a></sup></li><li>Tiny Tiny RSS/Debian 21~git20210204.b4cbc79+dfsg-1.2 (Unsupported) (<a href=http://tt-rss.org/>http://tt-rss.org/</a>)<sup class=footnote-ref><a data-footnote-ref href=#fn-tt-rss id=fnref-tt-rss>8</a></sup></li><li>UniversalFeedParser/5.1.3 +<a href=https://code.google.com/p/feedparser/>https://code.google.com/p/feedparser/</a></li><li><a href=https://nkanaev.com/works/yarr/>Yarr</a>/2.5</li></ul><p>From the bottom of my heart, thank you for reading. It means a lot to me that y'all actively choose to read my stuff. I see you, I appreciate you, this diversity is what keeps the web alive.</p><section class=footnotes data-footnotes><ol><li id=fn-elfeed><p>There are a whole two of you!! Congratulations on being first this is epic actually <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-elfeed>↩</a></p></li><li id=fn-feedparser><p>Presumably this is someone's personal tool and not just scraping giving the frequency but no way to be sure ig... Same goes for the few other libraries on this list. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-feedparser>↩</a></p></li><li id=fn-freshrss><p>This is the most popular one I see, makes sense it's the one I use too :3 <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-freshrss>↩</a></p></li><li id=fn-miniflux><p>After FreshRSS, this is the second-most-popular reader I see. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-miniflux>↩</a></p></li><li id=fn-netnewswire><p>My app of choice in conjunction with my FreshRSS server, can also work standalone tho! <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-netnewswire>↩</a></p></li><li id=fn-sharnoff><p>Someone built their own from completely from scratch I love it,,, <a aria-label="Back to reference 6" class=footnote-backref data-footnote-backref data-footnote-backref-idx=6 href=#fnref-sharnoff>↩</a></p></li><li id=fn-spacecowboys><p>I see more than one of this too. The android app is just called "Feeder", I like the author's name a lot better. <a aria-label="Back to reference 7" class=footnote-backref data-footnote-backref data-footnote-backref-idx=7 href=#fnref-spacecowboys>↩</a></p></li><li id=fn-tt-rss><p>This version is <em>definitely</em> unsupported, though the project has been kept alive <a href=https://github.com/tt-rss/tt-rss>under a new tt-rss GitHub Org</a> after the original maintainer left. <a aria-label="Back to reference 8" class=footnote-backref data-footnote-backref data-footnote-backref-idx=8 href=#fnref-tt-rss>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I Improved My Blog Drafting Setup And So Can You]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-01-23-i-improved-my-blog-drafting-setup-and-so-can-you/" />
      <id>https://wolfgirl.dev/blog/2026-01-23-i-improved-my-blog-drafting-setup-and-so-can-you/</id>
      <published>2026-01-24T02:19:36Z</published>
      <updated>2026-01-24T02:19:36Z</updated>
      <summary><![CDATA[Recently I was on a team for MIT Mystery Hunt and we came in 8th place which was very fun. I crashed hard on Monday from lack of puzzle-s...]]></summary>
      <content type="html"><![CDATA[<p>Recently I was on a team for MIT Mystery Hunt and we came in 8th place which was very fun. I crashed hard on Monday from lack of puzzle-shaped stimulation, and then on Tuesday went "welp enough being bored" so now y'all get to see me shoot off to the right on the <a href=https://rakhim.org/images/honestly-undefined/blogging.jpg>"number of posts about elaborate blog setups" chart</a>. you understand yes.</p><p>So anyways ! I got around halfway thru another "blogging about blgging" post when I thought it might benefit from some feedback, forgoing of my typical "one & done" approach to drafting. The way I normally go about this is:</p><ol><li>I attach the raw Markdown file to a Discord message.</li><li>My recipients open it in the editor of their choice (usually <a href=https://obsidian.md/>Obsidian</a> because it's really good).</li></ol><p>But that sucks. I can't edit the attachments after-the-fact, so if I want to make a tweak, I'd either have to give everyone a diff to apply, or re-send the entire file to everyone, both of which are annoying for all parties involved. "Surely there must be a better way !"</p><p>Turns out anything is possible on the computer provided you apply enough force. So I that's what I did!</p><h2><a aria-hidden=true class=anchor href=#attempt-1-what-i-have-currently-but-better id=heading-attempt-1-what-i-have-currently-but-better></a>Attempt 1: What I Have Currently, But Better</h2><p>So. I already have a <a href=https://wolfgirl.dev/blog/2024-11-08-what-if-anyone-could-use-my-post-composer-/>post composer</a>. And it already saves drafts to <a href=https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage><code>localStorage</code></a>. What if I could make it save drafts to the server instead, and then the server could provide a read-only rendered view of those drafts to anyone who knows a given UUID?</p><p><a href=https://github.com/p0lyw0lf/crossposter/blob/d8c85c32a5b745c3148d2ab5099a85ffd8e56c93/rc/src/rc/drafts.py>Turns out that's not too hard at all</a>! It's a few more hacks on top of an already-creaky codebase, but it does in fact work. There are buttons for "save draft" (saves to remote server), "edit draft" (loads from remote server), & "delete draft" (deletes on remote server), and an up-to-date lists of drafts is fetched on every page load.</p><p>However, this got me thinking: All the server is doing is managing markdown files, exactly how I'm working locally. Yet, despite being "just files" under the hood, the way I interacted with those files through the web interface was very... special. I still had to manually copy from my Obsidian vault where the "true" drafts lived, and the web-based file interface was much more clunky than anything I had natively. "Surely there must be a better way !"</p><h2><a aria-hidden=true class=anchor href=#attempt-2-re-thinking-the-interaction id=heading-attempt-2-re-thinking-the-interaction></a>Attempt 2: Re-Thinking The Interaction</h2><p>Of course there is. <strong>Anything</strong> is possible on the computer; I simply needed to provide more force. By the power of my three remaining brain cells colliding all at once, I came up with an Ideal Workflow:</p><ul><li>I can edit the files in Obsidian, locally, where I've always been editing them.</li><li>Said files near-instantly show up behind a URL, assuming I have internet to upload with.</li></ul><p>From there, it's just a matter of figuring out how to enable this :)</p><p>I already have <a href=https://syncthing.net/>Syncthing</a> set up for syncing drafts between my phone and my desktop, so I also added it to be synced to the <code>/var/opt/syncthing</code> directory on the server the post composer lives on, using Docker because I'm lazy (<a href=https://github.com/syncthing/syncthing/blob/cb391d25b8825140d029ef13b8dc9a6bc9b6209d/README-Docker.md>docs</a>):</p><pre class=syntax-highlighting><code># syncthing/docker-compose.yml
services:
  syncthing:
    image: syncthing/syncthing
    container_name: syncthing
    hostname: syncthing
    environment:
      - PUID=1001
      - PGID=1001
      - STGUIADDRESS=
    volumes:
      - /var/opt/syncthing:/var/syncthing
    network_mode: host
    restart: unless-stopped
    healthcheck:
      test: curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
      interval: 1m
      timeout: 10s
      retries: 3
</code></pre><pre class=syntax-highlighting><code># systemd/syncthing.service
[Unit]
Description=Syncthing via Docker Compose
Requires=docker.service
After=docker.service

[Service]
Restart=always
User=root
Group=docker
TimeoutStopSec=15
WorkingDirectory=/home/ubuntu/infrastructure/syncthing
ExecStartPre=/usr/bin/docker compose -f docker-compose.yml down
ExecStart=/usr/bin/docker compose -f docker-compose.yml up
ExecStop=/usr/bin/docker compose -f docker-compose.yml down

[Install]
WantedBy=multi-user.target
</code></pre><p>and, of course, we also want to expose the management interface remotely, just in case things go wrong (<a href=https://docs.syncthing.net/users/reverseproxy.html>docs</a>):</p><pre class=syntax-highlighting><code># nginx/rc.wolfgirl.dev.nginx
server {
	# ...
	location /syncthing/ {
		# Authenticate with ngx_http_auth_request_module, for my custom JWT authentication
		auth_request	/auth;

		# Needed in order for syncthing to know it's secure behind a reverse proxy
		proxy_set_header	Host "localhost";
		proxy_set_header	X-Real-IP $remote_addr;
		proxy_set_header	X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header	X-Forwarded-Proto $scheme;

		proxy_pass	http://localhost:8384/;

		proxy_read_timeout	600s;
		proxy_send_timeout	600s;
	}	
	# ...
}
</code></pre><p>Then, I can configure my website to read from a <em>subdirectory</em> of that synced folder. (It's important to be a subdirectory, since that means I can change the visibility of a draft at any time.) Finally, I can re-use the post renderer I made in Attempt 1, get rid of most the composer functionality, add a simple file explorer, and blam! Drafts :)</p><p>At this point, one might think "well why don't I just move my entire site onto this system?", but I have good reason not to: the finished post site has a very different performance profile compared to a drafts site. Because finished posts (1) change much less often and (2) serve much more traffic, I prefer to serve it from statically-generated files in an S3 bucket behind AWS Cloudfront w/ generous cache policies, whereas the drafts site is perfectly good to "re-render Markdown to HTML every time a request is made" on a VPS w/ no caching. There are tradeoffs with either approach, and the usecase requirements inform each deployment.</p><p>This does mean the markdown rendering/CSS for drafts vs finished posts are slightly different. Overall it's not a huge biggie, but at some point I would like to unify them. That is a fair bit harder tho, so I'll leave that for later :)</p><p>Thanks for reading, hope this inspires some more cursed setups!! :3</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Resolutions 2026 & Reflections]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2026-01-06-resolutions-2026-reflections/" />
      <id>https://wolfgirl.dev/blog/2026-01-06-resolutions-2026-reflections/</id>
      <published>2026-01-06T13:01:47Z</published>
      <updated>2026-01-06T13:01:47Z</updated>
      <summary><![CDATA[[ ] New Job [ ] Update Wardrobe (for real this time) [ ] Get on injections + prog How'd I Do Last Year?Before getting into my new resolut...]]></summary>
      <content type="html"><![CDATA[<ul class=contains-task-list><li class=task-list-item><input checked class=task-list-item-checkbox disabled type=checkbox> New Job</li><li class=task-list-item><input checked class=task-list-item-checkbox disabled type=checkbox> Update Wardrobe (for real this time)</li><li class=task-list-item><input class=task-list-item-checkbox disabled type=checkbox> Get on injections + prog</li></ul><hr><h2><a aria-hidden=true class=anchor href=#howd-i-do-last-year id=heading-howd-i-do-last-year></a>How'd I Do Last Year?</h2><p>Before getting into my new resolutions, let's take a look at my progress on <a href=https://wolfgirl.dev/blog/2025-01-05-new-year-s-resolutions/>last years' resolutions</a>:</p><blockquote><ul class=contains-task-list><li class=task-list-item><input checked class=task-list-item-checkbox disabled type=checkbox> 2025-07-22: <a href=https://wolfgirl.dev/blog/2025-07-22-i-did-my-first-v7-boulder-today-/>Do A V7 Boulder</a></li></ul></blockquote><p>This was an extremely soft V7, so under most circumstances it should not have counted, but I did it anyways!</p><p>I haven't updated my resolution for bouldering because V8s are still very far out of my wheelhouse; I'm <em>more</em> comfortable doing V5s than last year, but I'm still having to cut my teeth on real V6s before I can truly say I've done a V7. Climbing is mainly a social activity, so I'll be doing it regularly regardless.</p><blockquote><ul class=contains-task-list><li class=task-list-item><input class=task-list-item-checkbox disabled type=checkbox> Update My Wardrobe</li></ul></blockquote><p>Had to carry this one forward 😔</p><p>Fortunately, I have more concrete plans to achieve it this time around, with one thrift store tour already scheduled & another in the works. I don't yet feel comfortable going out in public in feminine clothes (which isn't helped by the fact I don't have any lol), let alone shopping for them, so having a group with me will be helpful. Hopefully getting some good outfits & having more occasions to wear them will change that.</p><blockquote><ul class=contains-task-list><li class=task-list-item><input class=task-list-item-checkbox disabled type=checkbox> Run a Half-Marathon (again)</li></ul></blockquote><p>Nope 😔</p><p>I have dialed in exactly what's been causing my headaches fortunately, but while "don't have allergic reactions" (solved w/ Flonase OTC nasal spray) & "don't become overly stressed" (solved by not doing that lol) are manageable, "don't jostle the head around for long periods" is <strong>most</strong> exacerbated by running. I've tried light jogs at various points in time, all with the same result. I am fairly sure it's the movement instead of the cardio because intense stationary bike is just fine.</p><p>It sucks to give up on this, because I loved running so much, but at least I've found other ways to get exercise.</p><blockquote><ul><li>blog more <ul><li>more HN frontpages 😎</li></ul></li></ul></blockquote><p>Somewhat of a success!! I did not blog nearly enough for my own liking, though I did get <a href="https://news.ycombinator.com/item?id=45681547">a Typescript post to HN front page</a> (don't look at the comments, I made that mistake). I don't really use HN tho, preferring to browse & participate in the <a href=https://lobste.rs>lobste.rs</a> community instead. HN is only notable for the amount of traffic it drives & re-aggregation that happens as a result.</p><blockquote><ul><li>go out more <ul><li>take more photos</li><li>make new friends</li><li>hang out with current friends more</li></ul></li></ul></blockquote><p>Triple check!! Again, still have room for improvement here, but I did do all of these things. Not really a resolution w/ a deadline, more of a standing attitude.</p><blockquote><ul><li>get better at Japanese <ul><li>level up a lot more in WaniKani (Japanese kanji learning app)</li><li>catch up with <em>Yotsuba&!</em> in Japanese</li></ul></li></ul></blockquote><p>DNF 😔 and no plans to continue it, because I have other things in mind. Good thing that WaniKani had a lifelong purchase option lol.</p><h2><a aria-hidden=true class=anchor href=#resolutions-for-this-year id=heading-resolutions-for-this-year></a>Resolutions for This Year</h2><p>When choosing these, I stuck to the criteria "what would be really frustrating if I hadn't achieved by next year?"</p><h3><a aria-hidden=true class=anchor href=#job id=heading-job></a>Job</h3><p>I am not super enthusiastic about my current job. Granted, by almost much any metric, I have it pretty darn good! Coworkers are great, pay/benefits are great, no complaints about how I'm treated. I do feel a little "stuck", tho, like I'm not working on significantly harder problems than when I first joined, despite theoretically having a lot more knowledge about how our systems work. I am the "go-to" when it comes to debugging some of these systems, which feels nice, but at a certain point debugging stops being exciting and is more "oh boy here i go fixing kludges again"<sup class=footnote-ref><a data-footnote-ref href=#fn-systems id=fnref-systems>1</a></sup>.</p><p>idk maybe it's just an ego thing & i need to check myself. But it really does feel like I'm not able to give a representative amount of effort compared to what I'm capable of, because I'm not excited by anything anymore. The company's direction is not my preferred direction as of late, especially compared to how it was early on. So I think it's time to look for greener pastures—after checking both sides of the fence to make sure the grass is truly greener ofc.</p><h3><a aria-hidden=true class=anchor href=#wardrobe id=heading-wardrobe></a>Wardrobe</h3><p>Yeah!!! hashtag fempilled hashtag girlmaxxing. See previous section.</p><h3><a aria-hidden=true class=anchor href=#hrt id=heading-hrt></a>HRT</h3><p>Similar to the job, it also feels like growth here has stalled. In some sense, stability here is good, don't want wild mood swings or crazy levels or anything. On the other hand, while smooth skin & small breasts r excellent additions, i would prefer "medium" or even "large" versions of the latter if at all possible. to fill out potential outfits ofc.</p><p>Genetics are likely not in my favor but hey we'll see. Everything I've read says injections are better than sublingual pills anyway, and while reviews of prog are mixed it's worth a shot at least I think. Current doctor doesn't want to change anything about my regimen so, uh, should do something about that first.</p><h2><a aria-hidden=true class=anchor href=#rapid-fire-other-goals id=heading-rapid-fire-other-goals></a>Rapid Fire Other Goals</h2><p>Less important than the main goals, but still quite nice to work towards:</p><ul><li>Better At Art <ul><li>I'm trying Drawabox again, seems better than last time I tried 4 years ago, we'll see how long it sticks tho. You'll know when I fall off <a href=https://bsky.app/profile/wolf.girl.technology/post/3mbjon63kdk2u>updating this Bluesky thread of beginner doodles</a></li></ul></li><li>Invite Friends To Things <ul><li>I am very good at saying "yes" when invited (mostly because I don't have a whole lot going on anyways), but no so much on the organizing front. This should change, because I always feel great when I get invited so time to pay that forward.</li></ul></li><li>Blogggggg <ul><li>I have a few projects that are in a perpetual unfinished state so I'd like to get those out and also maybe develop a better cadence than "once every 3 months or more".</li><li>Still going to keep up the album posts tho, it's a great forcing function to listen to more new albums.</li></ul></li></ul><hr><p>Anyways!! 2025 sure was a year huh. Only caught COVID once (proximate to the three times in 2024), went on trips with friends multiple times, so despite the rockiness in the world at large, it was an alright year all things considered. Reading some end-of-year accounts from others, I'd even go as far as to say I had a great year, and for that I am extremely grateful.</p><p>2026 has already started rocky in the large, but I'll do my best to not let that distract me from thriving. Here's hoping that your year will be on the up-&-up too.</p><p>And remember: EGGBUG LOVES YOU!!!</p><section class=footnotes data-footnotes><ol><li id=fn-systems><p>Doesn't help that I still seem to struggle with "systems design", at least at the fast pace of a startup. "Systems understanding" I can get just fine, but coming up with new things greenfield or proposing modifications to an existing system I understand, are skills I still need to develop. If anyone has recommended readings/practices beyond "just code more lol" I'd love to hear about it! Contact info is on my <a href=https://wolfgirl.dev/links/>links page</a> <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-systems>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rush To Buy These Queer Albums Before Bandcamp Friday Load Kills The Site (#9)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-12-05-rush-to-buy-these-queer-albums-before-bandcamp-friday-load-kills-the-site-9-/" />
      <id>https://wolfgirl.dev/blog/2025-12-05-rush-to-buy-these-queer-albums-before-bandcamp-friday-load-kills-the-site-9-/</id>
      <published>2025-12-05T18:53:42Z</published>
      <updated>2025-12-05T18:53:42Z</updated>
      <summary><![CDATA[PSYQUI – Escape From The Future PSYQUI was my introduction into the fascinating world of Japanese EDM, by way of those pirate Youtube cha...]]></summary>
      <content type="html"><![CDATA[<h2><a aria-hidden=true class=anchor href=#psyqui--escape-from-the-future id=heading-psyqui--escape-from-the-future></a><a href=https://psyqui.bandcamp.com/album/escape-from-the-future>PSYQUI – Escape From The Future</a></h2><p><img alt="PSYQUI walks into an old wooden door floating in the middle of an overgrown basketball field" src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/cover.png></p><p>PSYQUI was my introduction into the fascinating world of Japanese EDM, by way of those pirate Youtube channels that just put a simple visualizer on top of some anime art. The albums he contributed to back when he was most most active in 2018-2019, like <a href=https://megarex-jp.bandcamp.com/album/spd-gar-001>SPD GAR 001</a> and <a href=https://megarex-jp.bandcamp.com/album/techcore-evangelix-01>TECHCORE EVANGELIX 01</a>, absolutely still hold up to this day. His style is effect- & bass-heavy with a strong beat, one of those micro-genres where no one else can replicate it. It's been a long hiatus for sure, but I am very happy to see some more from him in a full EP!</p><h3><a aria-hidden=true class=anchor href=#favorite-track-02--why-dont-you id=heading-favorite-track-02--why-dont-you></a>Favorite Track: 02 – Why Don't You</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/02_Why_Dont_You.mp3></audio></p><p>(this was the only non-queer album I promise the rest are Very Queer lol)</p><h2><a aria-hidden=true class=anchor href=#poetically--queer-oddities id=heading-poetically--queer-oddities></a><a href=https://poetically.bandcamp.com/album/queer-oddities>Poetically – Queer Oddities</a></h2><p><img alt="A clocky Trans woman with slightly disheveled hair & makeup gazes shirtlessly at the camera" src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/cover.jpg></p><p>I found this artist somewhat randomly when their song Girls In High Heels came up in discovery a while ago. That's from their first album, <a href=https://poetically.bandcamp.com/album/odd-queer>Odd Queer</a>, and this is their B-sides follow up. Like the song that first enamored me, this album is very queer & a bit cheeky about it, distorted vocals & instruments leveraging the vocabulary of pop music to make something a little outside of it but still very catchy.</p><h3><a aria-hidden=true class=anchor href=#favorite-track-06--sun-of-a-beach id=heading-favorite-track-06--sun-of-a-beach></a>Favorite Track: 06 – Sun of a Beach</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/06_Sun_of_a_Beach.mp3></audio></p><h2><a aria-hidden=true class=anchor href=#passengerprincess--passengerprincess id=heading-passengerprincess--passengerprincess></a><a href=https://4kcarpet.bandcamp.com/album/passengerprincess>passengerprincess – PASSENGERPRINCESS</a></h2><p><img alt="The mascot, Passenger Puppy, has the album title written in highlights on their nose & green sparkles in their eyes to go with the purple theme" src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/PASSENGERPRINCESS.jpg></p><p>Apparently this was a meme in some furry circles when it first came out?? I overlapped just enough to find out about it, and my rule of thumb "if it's a meme, it's probably good, because a lot of people liked it" hasn't let me down.</p><p>This debut album definitely falls into the "hyperpop" genre, with similarities to artists like 100 gecs & Vylet Pony being readily apparent. The messages are a little more plainly stated, but I still get the sense we are laughing with the artist; it's very joyful & laser-targeted at the queer + furry spheres I inhabit.</p><h3><a aria-hidden=true class=anchor href=#favorite-track-04--4k-carpet id=heading-favorite-track-04--4k-carpet></a>Favorite Track: 04 – 4K CARPET</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/04_4K_CARPET.mp3></audio></p><h2><a aria-hidden=true class=anchor href=#june-lalonde--framemaker id=heading-june-lalonde--framemaker></a><a href=https://junelalonde.bandcamp.com/album/framemaker>June LaLonde – Framemaker</a></h2><p><img alt="An anthropomorphic animal walks away from a burned-out shadow in the grass, the chunk of land floating in the void" src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/Framemaker.jpg title=title></p><p>Before listening to this album, I did not understand what the big deal w/ guitar was, "why are people so obsessed with that instrument?". After listening to this album, now I understand. June LaLonde uses it to <em>incredible</em> effect, pulling emotions out of me I was not ready to feel yet ones I am glad to have felt anyways. It was written while she was grieving the deaths of people close to her, which checks out. The resulting pieces are sad, yes, but also hopeful, a fitting message for such a commemoration. My favorite song is one of the more up-beat ones, but I think this album deserves to be listened to in full. Give it a shot even if you too think it's out of your strike zone; it sure did hit for me at least.</p><h3><a aria-hidden=true class=anchor href=#favorite-track-06--land-of-kings id=heading-favorite-track-06--land-of-kings></a>Favorite Track: 06 – Land of Kings</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/019aefa5-d076-7881-948a-c286050adaa0/06_Land_of_Kings.mp3></audio></p><hr><p>I've had a blast listening to lots of albums this year! it's been a great way to consume music and I plan to keep this going into next year too. Hopefully there will continue to be Bandcamp Fridays to force my hand at periodic roundups like this, otherwise I tend not to remember lol. Anyways that is it for now, happy listening!!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[4 Unconventional Ways to Cast in Typescript]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-10-22-4-unconventional-ways-to-cast-in-typescript/" />
      <id>https://wolfgirl.dev/blog/2025-10-22-4-unconventional-ways-to-cast-in-typescript/</id>
      <published>2025-10-23T02:43:18Z</published>
      <updated>2025-10-23T02:43:18Z</updated>
      <summary><![CDATA[I saw a post by qntm and remembered I had a playground with a similar idea. I then expanded that playground into a (probably non-exhausti...]]></summary>
      <content type="html"><![CDATA[<p>I saw a <a href=https://bsky.app/profile/qntm.org/post/3m3dak7s63k2v>post by qntm</a> and remembered I had a playground with a similar idea. I then expanded that playground into a (probably non-exhaustive) list of ways to cast between arbitrary<sup class=footnote-ref><a data-footnote-ref href=#fn-arbitrary id=fnref-arbitrary>1</a></sup> types in Typescript:</p><h2><a aria-hidden=true class=anchor href=#convention-the-as-operator id=heading-convention-the-as-operator></a>Convention: The <code>as</code> Operator</h2><p>Ah, good ol' <code>as</code>:</p><pre class=syntax-highlighting><code class=language-ts><span class=keyword>const</span> <span class=function>cast</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=variable>a</span> <span class=keyword>as</span> <span class=type>unknown</span> <span class=keyword>as</span> <span class=type>B</span><span class=punctuation>;</span></code></pre><p>We can't just directly do <code>a as B</code> because Typescript is smart enough to warn us about <strong>that</strong>, at least. That very same error message also says,</p><blockquote><p>If this was intentional, convert the expression to 'unknown' first.</p></blockquote><p>So we can just do that :3</p><p>If we were approaching this from a type theoretic perspective, it's already done & dusted, we have the most cut-and-dry demonstration of unsoundness, pack it up go home.</p><p>But, what if we couldn't use <code>as</code>? Can we still get between two completely unrelated types?</p><h2><a aria-hidden=true class=anchor href=#unconvention-1-the-is-operator id=heading-unconvention-1-the-is-operator></a>Unconvention 1: The <code>is</code> Operator</h2><p><code>is</code> is commonly used for for interfacing with Typescript's flow-typing system, helping it figure out what exactly the return value of a boolean function <em>means</em>. For example:</p><pre class=syntax-highlighting><code class=language-ts><span class=keyword>const</span> <span class=function>notUndefined1</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span> <span class=operator>|</span> <span class=constant>undefined</span><span class=punctuation>)</span>: <span class=type>boolean</span> <span class=operator>=></span> <span class=variable>a</span> <span class=operator>!==</span> <span class=constant>undefined</span><span class=punctuation>;</span>
<span class=keyword>const</span> <span class=function>notUndefined2</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span> <span class=operator>|</span> <span class=constant>undefined</span><span class=punctuation>)</span>: <span class=variable>a</span> is <span class=type>A</span> <span class=operator>=></span> <span class=variable>a</span> <span class=operator>!==</span> <span class=constant>undefined</span><span class=punctuation>;</span>

<span class=keyword>const</span> <span class=variable>maybeNumber0</span>: <span class=type>number</span> <span class=operator>|</span> <span class=constant>undefined</span> <span class=operator>=</span> <span class=function>someExternalFunction</span><span class=punctuation>();</span>
<span class=keyword>if</span> <span class=punctuation>(</span><span class=variable>maybeNumber0</span> <span class=operator>!==</span> <span class=constant>undefined</span><span class=punctuation>)</span> <span class=keyword>return</span><span class=punctuation>;</span>
<span class=comment>// Thanks to flow-typing, Typescript knows that `maybeNumber0: number`</span>
<span class=comment>// if we get here.</span>
<span class=keyword>const</span> <span class=variable>maybeNumber1</span> <span class=operator>=</span> <span class=function>someExternalFunction</span><span class=punctuation>();</span>
<span class=keyword>if</span> <span class=punctuation>(</span><span class=function>notUndefined1</span><span class=punctuation>(</span><span class=variable>maybeNumber1</span><span class=punctuation>))</span> <span class=keyword>return</span><span class=punctuation>;</span>
<span class=comment>// However, Typescript cannot infer flow from ordinary functions;</span>
<span class=comment>// At this point, it still thinks `maybeNumber1: number | undefined`</span>
<span class=keyword>const</span> <span class=variable>maybeNumber2</span> <span class=operator>=</span> <span class=function>someExternalFunction</span><span class=punctuation>();</span>
<span class=keyword>if</span> <span class=punctuation>(</span><span class=function>notUndefined2</span><span class=punctuation>(</span><span class=variable>maybeNumber2</span><span class=punctuation>))</span> <span class=keyword>return</span><span class=punctuation>;</span>
<span class=comment>// The `is` annotation has the exact same `boolean` value at runtime,</span>
<span class=comment>// but provides extra information to the compiler, so Typescript can know</span>
<span class=comment>// that `maybeNumber2: number` if we get here.</span></code></pre><p>However, <code>is</code> is sort of an escape hatch outside the regular typing system, and we can abuse it to tell the compiler whatever we want:</p><pre class=syntax-highlighting><code class=language-ts><span class=keyword>const</span> <span class=function>badDetector</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=punctuation>{</span>
    <span class=keyword>const</span> <span class=function>detector</span> <span class=operator>=</span> <span class=punctuation>(</span><span class=variable>_ab</span>: <span class=type>A</span> <span class=operator>|</span> <span class=type>B</span><span class=punctuation>)</span>: <span class=variable>_ab</span> is <span class=type>B</span> <span class=operator>=></span> <span class=constant>true</span><span class=punctuation>;</span>
    <span class=keyword>if</span> <span class=punctuation>(</span><span class=function>detector</span><span class=punctuation>(</span><span class=variable>a</span><span class=punctuation>))</span> <span class=keyword>return</span> <span class=variable>a</span><span class=punctuation>;</span>
    <span class=keyword>throw</span> <span class=keyword>new</span> <span class=type>Error</span><span class=punctuation>(</span><span class=string>"unreachable"</span><span class=punctuation>);</span>
<span class=punctuation>};</span></code></pre><p>Typescript doesn't (and can't!) check that the function body is actually doing what the <code>is</code> assertion says. So we can just write a bad one on purpose! (Or on accident, introducing quite a subtle bug.)</p><h2><a aria-hidden=true class=anchor href=#unconvention-2-mutation-across-boundaries id=heading-unconvention-2-mutation-across-boundaries></a>Unconvention 2: Mutation Across Boundaries</h2><p>This cast requires a "seed" value <code>b: B</code> in order to be able to cast <code>a: A</code> to <code>B</code>, but make no mistake: this sort of thing can come up fairly often if we're not careful about how we mutate objects.</p><pre class=syntax-highlighting><code class=language-ts><span class=keyword>const</span> <span class=function>mutation</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span><span class=punctuation>,</span> <span class=variable>b</span>: <span class=type>B</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=punctuation>{</span>
    <span class=keyword>const</span> <span class=function>mutate</span> <span class=operator>=</span> <span class=punctuation>(</span><span class=variable>obj</span>: <span class=punctuation>{</span> <span class=property>field</span>: <span class=type>A</span> <span class=operator>|</span> <span class=type>B</span> <span class=punctuation>})</span>: <span class=type>void</span> <span class=operator>=></span> <span class=punctuation>{</span>
        <span class=variable>obj</span><span class=punctuation>.</span><span class=property>field</span> <span class=operator>=</span> <span class=variable>a</span><span class=punctuation>;</span>
    <span class=punctuation>};</span>

    <span class=keyword>const</span> <span class=variable>obj</span> <span class=operator>=</span> <span class=punctuation>{</span> <span class=property>field</span>: <span class=variable>b</span> <span class=punctuation>};</span>
    <span class=function>mutate</span><span class=punctuation>(</span><span class=variable>obj</span><span class=punctuation>);</span>
    <span class=keyword>return</span> <span class=variable>obj</span><span class=punctuation>.</span><span class=property>field</span><span class=punctuation>;</span>
<span class=punctuation>};</span></code></pre><p>I showed this to a type theory friend and their reaction was:</p><blockquote><p>bruh ts type system mega fails Variance is hard xd xd xd</p></blockquote><p>What they meant by that was, the coercion from <code>{ field: B }</code> to <code>{ field: A | B }</code> is unsafe when the destination <code>field</code> is mutable; if we allow it, we get exactly the behavior shown here. To make it safe we'd need <code>{ readonly field: A | B }</code>, which then prevents the mutation.</p><p>Another way of thinking about this is, Typescript currently has no way to "flow" the cast of/assignment to <code>obj.field</code> after the function runs. (Potentially on purpose, because that would make the type system even more complex & limit certain useful patterns.) Inlining the <code>obj.field = a;</code> allows us to catch this, but the analysis does not go across function boundaries.</p><h2><a aria-hidden=true class=anchor href=#unconvention-3-smuggling-through-structural-typing id=heading-unconvention-3-smuggling-through-structural-typing></a>Unconvention 3: Smuggling Through Structural Typing</h2><p>Typescript is structurally typed. This means that, if we have an <code>obj: { field: string }</code>, all we know is that there exists an <code>obj.field: string</code>. Typescript doesn't care at all if <code>obj</code> has other fields, and in fact this is the biggest advantage of structural typing: we can freely "upcast" to less restrictive types (i.e. fewer fields) without having to change runtime representations.</p><p>The downside of this sort of upcasting is that, some operations like <code>Object.values</code>/the spread operator are only properly typed when they have a complete list of fields, and have their assumptions violated when extra fields are in the mix:</p><pre class=syntax-highlighting><code class=language-ts><span class=keyword>const</span> <span class=function>loopSmuggling</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span><span class=punctuation>,</span> <span class=variable>b</span>: <span class=type>B</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=punctuation>{</span>
    <span class=keyword>const</span> <span class=variable>objAB</span> <span class=operator>=</span> <span class=punctuation>{</span> <span class=property>fieldA</span>: <span class=variable>a</span><span class=punctuation>,</span> <span class=property>fieldB</span>: <span class=variable>b</span> <span class=punctuation>};</span>
    <span class=keyword>const</span> <span class=variable>objB</span>: <span class=punctuation>{</span> <span class=property>fieldB</span>: <span class=type>B</span> <span class=punctuation>}</span> <span class=operator>=</span> <span class=variable>objAB</span><span class=punctuation>;</span>
    <span class=keyword>for</span> <span class=punctuation>(</span><span class=keyword>const</span> <span class=variable>field</span> <span class=keyword>of</span> <span class=type>Object</span><span class=punctuation>.</span><span class=function>values</span><span class=punctuation>(</span><span class=variable>objB</span><span class=punctuation>))</span> <span class=punctuation>{</span>
        <span class=comment>// Object.values believes all fields have type `B`,</span>
        <span class=comment>// but actually `fieldA` is first in iteration order.</span>
        <span class=keyword>return</span> <span class=variable>field</span><span class=punctuation>;</span>
    <span class=punctuation>}</span>
    <span class=keyword>throw</span> <span class=keyword>new</span> <span class=type>Error</span><span class=punctuation>(</span><span class=string>"unreachable"</span><span class=punctuation>);</span>
<span class=punctuation>};</span>

<span class=keyword>const</span> <span class=function>spreadSmuggling</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span><span class=punctuation>,</span> <span class=variable>b</span>: <span class=type>B</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=punctuation>{</span>
    <span class=keyword>const</span> <span class=variable>objA</span> <span class=operator>=</span> <span class=punctuation>{</span> <span class=property>field</span>: <span class=variable>a</span> <span class=punctuation>};</span>
    <span class=keyword>const</span> <span class=variable>obj</span>: <span class=punctuation>{}</span> <span class=operator>=</span> <span class=variable>objA</span><span class=punctuation>;</span>
    <span class=keyword>const</span> <span class=variable>objB</span> <span class=operator>=</span> <span class=punctuation>{</span> <span class=property>field</span>: <span class=variable>b</span><span class=punctuation>,</span> ...<span class=variable>obj</span> <span class=punctuation>};</span>
    <span class=comment>// `objB.field` has been overwritten by the spread,</span>
    <span class=comment>// but Typescript doesn't know that.</span>
    <span class=keyword>return</span> <span class=variable>objB</span><span class=punctuation>.</span><span class=property>field</span><span class=punctuation>;</span>
<span class=punctuation>};</span></code></pre><p>These casts have the same restriction as (2), in that we require a "seed" value <code>b: B</code> in order to make it typecheck. Still, it's a bit of a double-whammy, because trying to avoid (2) by copying objects with a <code>...</code> spread can make you run smack-dab into this one elsewhere.</p><h2><a aria-hidden=true class=anchor href=#unconvention-4--void-is-very-bad id=heading-unconvention-4--void-is-very-bad></a>Unconvention 4: <code>| void</code> is Very Bad</h2><p>This one's by far the most unconventional; the rest you're probably aware of if you've worked with Typescript for a while, but this one hardly ever comes up because it's such a "why even do this" kinda deal. Still, I have seen it in my work's codebase (and immediately excised it once I realized), so it's not <em>impossible</em> to come across.</p><p>Anyways here it is:</p><pre class=syntax-highlighting><code class=language-ts><span class=keyword>const</span> <span class=function>orVoid</span> <span class=operator>=</span> <span class=operator>&lt;</span><span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>,</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>a</span>: <span class=type>A</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=punctuation>{</span>
    <span class=keyword>const</span> <span class=function>outer</span> <span class=operator>=</span> <span class=punctuation>(</span><span class=variable>inner</span>: <span class=punctuation>()</span> <span class=operator>=></span> <span class=type>B</span> <span class=operator>|</span> <span class=type>void</span><span class=punctuation>)</span>: <span class=type>B</span> <span class=operator>=></span> <span class=punctuation>{</span>
        <span class=keyword>const</span> <span class=variable>b</span> <span class=operator>=</span> <span class=function>inner</span><span class=punctuation>();</span>
        <span class=keyword>if</span> <span class=punctuation>(</span><span class=variable>b</span><span class=punctuation>)</span> <span class=keyword>return</span> <span class=variable>b</span><span class=punctuation>;</span>
        <span class=keyword>throw</span> <span class=keyword>new</span> <span class=type>Error</span><span class=punctuation>(</span><span class=string>"falsy"</span><span class=punctuation>);</span>
    <span class=punctuation>};</span>

    <span class=keyword>const</span> <span class=function>returnsA</span> <span class=operator>=</span> <span class=punctuation>()</span>: <span class=type>A</span> <span class=operator>=></span> <span class=variable>a</span><span class=punctuation>;</span>
    <span class=keyword>const</span> <span class=variable>voidSmuggled</span>: <span class=punctuation>()</span> <span class=operator>=></span> <span class=type>void</span> <span class=operator>=</span> <span class=variable>returnsA</span><span class=punctuation>;</span>
    <span class=keyword>return</span> <span class=function>outer</span><span class=punctuation>(</span><span class=variable>voidSmuggled</span><span class=punctuation>);</span>
<span class=punctuation>};</span></code></pre><p>This is a combination of a few interesting things. For Typescript, <code>void</code> is primarily seen as a function's return value, indicating "I don't care what this function returns because I'm not going to use it". This is why any function, including our <code>() => A</code> one, can be safely coerced to <code>() => void</code>. Usually, this is safe, because once we have a <code>() => void</code>, we really can't assign its output to a variable, nor can we directly type a value as <code>void</code>; it's a very special type after all.</p><p>However, <code>void</code> can still participate in type combinations like <code>B | void</code>. And, because functions are <a href=https://www.typescriptlang.org/docs/handbook/2/generics.html#variance-annotations>covariant</a> in their return type, <code>() => void</code> can be safely coerced to <code>() => B | void</code>. And, as it turns out, we <em>can</em> assign that <code>B | void</code> return type to a variable!</p><p>If <code>void</code> were meant to be assigned directly, it <em>should</em> behave something more like <code>any</code> or <code>unknown</code>. But it's not, so instead it behaves like a <a href=https://developer.mozilla.org/en-US/docs/Glossary/Falsy>falsy</a> type, because a normal <code>void</code>-returning function actually returns <code>undefined</code> at runtime. This is how we're able to <code>if (b) return b;</code> (which is <strong>not</strong> the same as checking <code>b</code>'s true type!) & still have everything typecheck.</p><p>Unfortunately, that means this cast only works for truthy <code>a</code>. But that's not too much of an issue I think, the Cool Factor outweighs this limitation :3</p><h2><a aria-hidden=true class=anchor href=#does-this-even-matter id=heading-does-this-even-matter></a>Does This Even Matter?</h2><p>Yes, but it's complicated.</p><p>On the one hand, Typescript is clearly just a "best effort" at adding types to Javascript, and it does a darn good job at that. If you're holding it right, these things don't come up, and your code genuinely is much much safer than if you used raw Javascript.</p><p>On the other hand, all these "unconventions" are real footguns one can stumble into & unintentionally introduce unsafety into your codebase. It only takes a little bit of unsoundness in one place to render entire swaths buggy. We can do our best to avoid these patterns manually, but an automated solution will always be better at catching them.</p><h2><a aria-hidden=true class=anchor href=#what-can-we-do-about-this id=heading-what-can-we-do-about-this></a>What Can We Do About This?</h2><p><strong>TL;DR</strong> use <a href=https://typescript-eslint.io/>typescript-eslint</a><sup class=footnote-ref><a data-footnote-ref href=#fn-typescript-eslint id=fnref-typescript-eslint>2</a></sup>. While neither Typescript<sup class=footnote-ref><a data-footnote-ref href=#fn-typescript id=fnref-typescript>3</a></sup> nor Eslint<sup class=footnote-ref><a data-footnote-ref href=#fn-eslint id=fnref-eslint>4</a></sup> on their own come with enough rules to detect any of these, the typescript-eslint ruleset includes things like <a href=https://typescript-eslint.io/rules/prefer-readonly-parameter-types/>@typescript-eslint/prefer-readonly-parameter-types</a> (prevents (2)), <a href=https://typescript-eslint.io/rules/no-invalid-void-type/>@typescript-eslint/no-invalid-void-type</a> (prevents (4)), & <a href=https://typescript-eslint.io/rules/no-unnecessary-type-parameters/>@typescript-eslint/no-unnecessary-type-parameters</a> (prevents the rest by making the <code>unknown</code> viral). Unfortunately, all of them are opt-in, and Typescript + eslint + typescript-eslint always requires a fair bit of mucking about to get working.</p><p>Anyways, I hope these examples are enough to convince you to use more aggressive linting on your Typescript projects in the future :3</p><section class=footnotes data-footnotes><ol><li id=fn-arbitrary><p>OK, I may have over-exaggerated on the "arbitrary" part a little :P (1) and (2) do in fact work for everything, but (3) doesn't work for <code>undefined</code> sometimes and (4) only works if the <code>a</code> to be cast is truthy. Still, it's a good enough demonstration I think. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-arbitrary>↩</a></p></li><li id=fn-typescript-eslint><p><a href="https://typescript-eslint.io/play/#ts=5.9.2&fileType=.tsx&code=PQKhAIGEHsDsDcCmsAuBLO4TAFAGM4BnFcPAQ2PAF5wAeAQQBpwAhRgPgAoyAucegJR8W1duDLjC4AK6wA1rGgB3WJNYBuHDlAQAqrAIJk6TAEY%2BAFQAWicAAM0hO%2BADyAB0QAnMimiesuIaUAEZkACYAIogoiHi%2B-jQMzGxcvPxCrKLgAN444PmkRCRh0bHx1OCcAPpkwXz04AA%2BrBk1weCOmVRiKJ7SiJoFHQBmlSUxcX7cAgLgntHSnqpkgwUoVp7K4LCISuAAop6bnpwARLLzZHhWtQA2iKcCmgC%2Bmtpg4PqGSKgYqgBMfAAstIUD4-vw8JtCFIWNBZGEyJ40IgpNh8EVwABbUHgzCJJisDjcerMOotYRZXJDIIkHFgmIVTjQYIAKz42XAwxRtzC9SamWeGXg0DQYSpeSGBRZrIAdNzELyKitJflXloaZiZRVsgreXxguqhvSfIhmWynqq5gsluAZfKeWEXm8dJ8DHAfiZVABmWXmcAAZRxAHNg7c0LBg%2BBrJtpMGrIHetI4osyLdowBPNwRqPo2ngW7QaBuINxsM5ipJImpUngcksDIibo5K35mX0Js5LmO%2Bh8MjMPVhFgG8BGgpttnDruDqciZ4VdssVb5YZ%2BSr5wd20YuNllWXwNP9Qjm1kN2bUqX5YDAVy7uL7w%2BouuKlFIKRp9ODqQ3JDgFBZ2w7BYOxGCtIZrzrUFxBTD8M3sQd6GcTpuU8SgIw6GJvC9O1PBKTxZTAgp5hQRZVEHZdRytdZNj2HY9kOY4zguRArhuYJ7keZ0tFdL4PWMCFfUBQMQ3LSNow2eF40TPoU28dMLCzCs80xQg3EuMJS1DcMxIJZJiTSQl60bCVNVgSh2x1btFT5cRRwoid2RyecaHbeytUnSzBwNZhZV87UxyvG87BlFgHWs5wbikYJEGQO0kE8JRkRQGJVGCOD1lsVT1NA8Cb2CKCFI8QgoTQNximgVFYAAchIBQtnWHwCKGYjSLtScwt5Lj3j0d0jF%2BTAABY%2BGcZoRTFJCpAANS8OCWHCAIMTMkg-Em0VxV06sSXSSlmwvfI21BLwmQjHZPD4ThZmbERRrW4zdsI-bMXaGgTq8C6KKGNBRk4YJZha21gg%2BtYJNo3YDiOKZTmGNNCAzTirXVVtMX%2BsyGhoC7%2BWbFVTMoMaNJE%2B4bIurI8YqFHCHoCiUbtQ6TjxzSw0QMJLXVIA&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQAIBcBPABxQGNoBLY-AWhXkoDt8B6AQwBMArdsxFrQD2AN0TR4QrrWSUA5k3b5Y0FOijjoQ6JAA04bHiKlkFanQbM27aNHaFaxxOsibtegzgIlyVGvWRGFg4Ad3ZKOnwACwF2ACMkFzcdfWwobxMzf0tguPYmR2RaMiEAW1KBfCTbd1TDDN9zAKC2PIL8QKtisoqWaq0Uz3SnUz8LTuCyeHZkIsZ8cXZ4WmItUmgiGSJEjFcawbSjH1GmnLYpmaKK6KFOIthkREcoylRd5I9DhpPsifOhJiyZALQRyATiShkbqA-BwMj4bRvDT7T71EZZcYtVglQGvEF0ZicRAAD0QnGEcW4iHhW0IO2RA1RXnRY2aVmxAKB%2BNoqmU0CY-VqQyOmVZZw5uOBlUcPlol3E%2BEonMFBzRxwxbMmnLx0qctCJADNmBElYCVUzhuqxX8JVzdbKScRtB1zXVmVbTjacXbBHrKKUnRskXtGW7LaLPVjDewEHRiDZ2KVaNNga7hd8NeLOEI6Ewc0pTWmvizI%2BzHYwyBFaAbYEx4aaeYg%2Be0fEW1RHfljy5CqxVSnFxHKyPxZpQ4pR5oQ2%2B6O5iy8TiBXe7cEE84kJa5wbA4RtPw41O%2BzjXQiRc7Irle8UWGRQe58FSuxiSsE6Vgx8bxnrVi%2BwPoMJoCJKgmDkPdbx%2Be82GuKJbhkeRFD5J5gTpZwr1DdMS0PYJFFKZg5GhMQWELNChWLD0sLYPM5VsexoWBOEER0EjVRnO9NUooRqLsBwiSQBYwK-UtsM4vJHkcTj6LwgTMMg1gqJxGtZBA2g8wKJgEGWeUNmIhlSPbNjxXkgFFLw2gRCEShyUdVRRwBaTyNkqiiVWaklDJezZ3YuTOM4WBSGKFMrkQftxHfa8MIcrynL8is3PodSkxEJZYDUZiLXAzMbWixdITivVvUVZRKjC9CyM8wyfMIHDIX1RA%2BNQ3SWP3CCos44KaAcGs6wvAU0s-GTWvoANNiscQDT4BqQz01iWoqoaOopKkaScDyDKytqFyXOh8inPqIvK9b6GJWF2BUgEVI0uVZgVHSpqajLv3ZKiSROphEA3IoLlmVbZsOg1JALZTVjKV5Usa9LBIo7zq20WhmC4%2BwfsyrEqP9HKyXoJL4CRx7hLhgNnRlfzZCJegDQNakXT2sq1pRzjmApmiEieXdqf0366bhpgscs55XhxoSOK5nnyXM3mVrZmbkaezjJCEYhq1rMgBahqjJFmYQDRWVRK1kOzJea6W8cfOQaoSv8Sumw3caFip8jwpANbF8l5cWRiVcczjcOQB4MbekIPcG73ffJYHvbBu6IYGubg8eclkBcrhA5jyhSSshLLfuyHPZUxMUHjfhk8O1SLvgTTro2P2NNeKJuiWcgpINh7Behkv1LLq7Hkrl2aFNJZiiicJevB-rIrmtvLq0nqi851RT2mVQZ5lxtfKYLdfVlAqIhSlhM6jsfDtUABHWBKFUfHAypkf9tp5ebNhSEFnJNHnT30eDtnlAH-hDHWevmmObL2QIPbMAcm7Z0GtEV4cpGAzCXnjPUSxKBwPAdHQ6tY3ojmQNuWg64hBIHyMmCIixlglADDYSaH4b6ALxhg6kKBsHQAcDiTgJp9b-3ZkbIWdCsE4PjHYa4g5gbrE2JceCvQqioIPpzHhDCcEnyQUacQ8DuFMEwXIphjh2rTAWEdFytlh6R3frfWhaj6GzBwYg6AchYASLftQrh0NZEWM0Ygiu08pEf2Xs4xhO5N6chOlYFRTizG8Ncf47moVbpUIAY4qiPjLGyn4XnBY0B7GxJtiE7BFNqI2IkcE%2BJgJ2A5LEQofJniTGqOyU8MgSxsYVJoVU4pTx54Jh6rQCo1jG4cKlpkwp1T4q2O6OQqgyB2FGIcX0zitYBldXrOdCWPTrYt36c0jpwU-xDiwQU6ZRScm8hUIYmJnCpm0BmWstxXcPFLObqrXZAzaw4NwupdJJyVm7JDnomyeszQNLiR8uOZkbCvN6e8s5YkBwGm0C0xARo3o7PBXVBhdFYSwHhO4P5pzfaOyKO1TYjpnQIqSv%2BY%2Bp8bIIpCHYYg6xFqUyJhHY5oK7lqQ0vEJA9LO43XOshekEyMktwBPAHcUQtAhHoCiTFLdkmCP-MIm6DLwr8qhi5RmV0UXBJVYOIk9E0V8m6Xyt5yrVCqoEEM48yDGAAC9QoauNYOOFnBbWwvtbDIQBonWqrme0xZBqmWyU1f%2BZgUxYDao9YOeYJDBlJl-MoyVRrnX-hwvnCatAADWiBCAhG0I6uN-q7WJursAuujtKwgTDf%2BeWPV%2B5kEHswct2sQZiVUEtCw%2BwQXLPjaq1QXBBW7RuRA8UAbGw9qYEKl8AimyDj-r6jteaE0rzRSzJJr5J1MX7WgrEQ7VBggXEdak9bJLKWBDYDo8U7i0BCBEKI9aDn8j5kUH1jLZ1eSHaex0dKPi5q8qoHRGMwT4FSTIJsgH4xn3bbcvNjanheobDMKqysv3ilJWfJ4NhuIyEJmQ-hlDFWGtksh8%2B7AwgRGCffKgNJFwPGEOsfIdxSNf3I5EbRcVrIMNNOBgdNpb0FCI%2BESR67pHsjGRsDlW8iq72CZJGkeCCEFFYwYjjG6hOXvwDWo6g8HiKkIsimt1JU3BIfouJCKZa75oEIXRDNonCGmCbWdcm51k3BzQJrxwRayUCUfHeCSgVAKtKnhryvszlMFTXmEIBR4a1NU7XWpZc8hkFTUCqgbKcOMgMAAXxAOloAA&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false">typescript-eslint playground</a> <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-typescript-eslint>↩</a></p></li><li id=fn-typescript><p><a href=https://www.typescriptlang.org/play/?#code/PQKhAIGEHsDsDcCmsAuBLO4TAFAGM4BnFcPAQ2PAF5wAeAQQBpwAhRgPgAoyAucegJR8W1duDLjC4AK6wA1rGgB3WJNYBuHDlAQAqrAIJk6TAEY+AFQAWicAAM0hO+ADyAB0QAnMimiesuIaUAEZkACYAIogoiHi+-jQMzGxcvPxCrKLgAN44AJBBJGHRsfHU4JwA+mTBfPTgAD6sGdXB4I6ZVGIontKImnloAGYVxTFxftwCAuCe0dKeqmQDKFaeyuCwiErgAKKe656cAESyc2R4VjUANojHApoAvpraYOD6hkioGKoATHwAWWkKB8P34eHWhCkLGgsjCZE8aEQUmw+CIJAAtsDQZhEkxWBxuHVmLVmsIsrkCujwFiQTFypxoMEAFZ8bLgIZI65hOqNTKPGZdHL5PJM5kAOk5iG55WW+WeWipsEoYvK7Kl3L4bQVeVpPkQjJZD3ycxQC1UYslXLCTy0OneBjgXxMqgAzOLzOAAMpYgDmvuuaFgvvA1nW0l9Vm9PWkcQWZGuoYAnm4gyHUYVwNdoNA3D6IwG0+UkgTUsTwKSWBkRELKZmxfQazkOdb6HwyMwNWEWFrwDr6yye82u0ORI9yg2WAMhn4Kpmu+BoCMXCzSuL4Am+oRDcyqzNKXlgMBXKu4uvN8iK9KkUgpAnE12pFckOAUCnbHYWHZGCKjxXgeIcb3km9hdvQzgdJyniUEG7QxN4LqLp4xSeOKIqmuaLbSja8o4OA+GvmsGxbDs+yHCcZyIBcVzBLc9y2q8eiOkY3yYO6-zen6hbBqGRERlGXoxnG3iJhYKZFhm1KEG45xhPm-qBjxeLJISaT4pW1YUvkA7MvUNDqta7Z9gMOlsuONANiZ1Jik2BnYVqzDik5qo6n+dg2Va2HOFcUjBIgyCLkgnhKIiKAxKowQgastjSbJP6HsewQAWJHiEBCaBuEU0DIrAADkJAKBsqw+GheQYYsi6Dp53IMfaHxOsYYIACx8M4TTwNAaBhBBUgAGpeCBLDhAEaLKiQfi9Z1YTFviKREuk5K1tp1nAl4DJBlsnh8JwgpiCI7VTZpS15EqITlBtXg7QMgwjJwwQzOVEXXas6w7CRewHJMxxDAmhBJvRuHLWNszzIshB6RUu3iFZwMdV18kBogPKQ1kcPTTQj3gwMj2LqtRxowjtxhMajxAA>typescript playground</a> <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-typescript>↩</a></p></li><li id=fn-eslint><p><a href="https://eslint.org/play/#eyJ0ZXh0IjoiLy8gRGlzYWJsaW5nIGEgYnVuY2ggb2YgdGhpbmdzIHRoYXQgaGF2ZSBubyBpbXBhY3Qgb24gdHlwaW5nXG4vKiBlc2xpbnQgbm8tdW51c2VkLXZhcnM6IDAgKi9cbi8qIGVzbGludCBpZC1sZW5ndGg6IDAgKi9cbi8qIGVzbGludCBvbmUtdmFyOiAwICovXG4vKiBlc2xpbnQgb2JqZWN0LXByb3BlcnR5LW5ld2xpbmU6IDAgKi9cbi8qIGVzbGludCBwYWRkZWQtYmxvY2tzOiAwICovXG4vKiBlc2xpbnQgbmV3bGluZS1hZnRlci12YXI6IDAgKi9cbi8qIGVzbGludCBuZXdsaW5lLWJlZm9yZS1yZXR1cm46IDAgKi9cbi8qIGVzbGludCBjb21tYS1zcGFjaW5nOiAwICovXG4vKiBlc2xpbnQgY3VybHk6IDAgKi9cbi8qIGVzbGludCBxdW90ZS1wcm9wczogMCAqL1xuLyogZXNsaW50IG9iamVjdC1jdXJseS1zcGFjaW5nOiAwICovXG4vKiBlc2xpbnQgbXVsdGlsaW5lLWNvbW1lbnQtc3R5bGU6ICovXG4vKiBlc2xpbnQgY2FwaXRhbGl6ZWQtY29tbWVudHM6IDAgKi9cblxuLyoqIENvbnZlbnRpb24gKi9cbmNvbnN0IGNhc3QgPSA8QSwgQiw+KGE6IEEpOiBCID0+IGEgYXMgdW5rbm93biBhcyBCO1xuXG4vKiogVW5jb252ZW50aW9uIDE6IFRoZSBgaXNgIE9wZXJhdG9yICovXG5jb25zdCBiYWREZXRlY3RvciA9IDxBLCBCLD4oYTogQSk6IEIgPT4ge1xuICAgIGNvbnN0IGRldGVjdG9yID0gKF9hYjogQSB8IEIpOiBfYWIgaXMgQiA9PiB0cnVlO1xuICAgIGlmIChkZXRlY3RvcihhKSkgcmV0dXJuIGE7XG4gICAgdGhyb3cgbmV3IEVycm9yKFwidW5yZWFjaGFibGVcIik7XG59O1xuXG4vKiogVW5jb252ZW50aW9uIDI6IE11dGF0aW9uIEFjcm9zcyBCb3VuZGFyaWVzICovXG5jb25zdCBtdXRhdGlvbiA9IDxBLCBCLD4oYTogQSwgYjogQik6IEIgPT4ge1xuICAgIGNvbnN0IG11dGF0ZSA9IChvYmo6IHsgZmllbGQ6IEEgfCBCIH0pID0+IHtcbiAgICAgICAgb2JqLmZpZWxkID0gYTtcbiAgICB9O1xuXG4gICAgY29uc3Qgb2JqID0ge2ZpZWxkOiBifTtcbiAgICBtdXRhdGUob2JqKTtcbiAgICByZXR1cm4gb2JqLmZpZWxkO1xufTtcblxuLyoqIFVuY29udmVudGlvbiAzLjE6IFNtdWdnbGluZyBUaHJvdWdoIFN0cnVjdHVyYWwgVHlwaW5nICovXG5jb25zdCBsb29wU211Z2dsaW5nID0gPEEsIEIsPihhOiBBLCBiOiBCKTogQiA9PiB7XG4gICAgY29uc3Qgb2JqQUIgPSB7IGZpZWxkQTogYSwgZmllbGRCOiBiIH07XG4gICAgY29uc3Qgb2JqQjogeyBmaWVsZEI6IEIgfSA9IG9iakFCO1xuICAgIGZvciAoY29uc3QgZmllbGQgb2YgT2JqZWN0LnZhbHVlcyhvYmpCKSkge1xuICAgICAgICAvLyBPYmplY3QudmFsdWVzIGJlbGlldmVzIGFsbCBmaWVsZHMgaGF2ZSB0eXBlIGBCYCxcbiAgICAgICAgLy8gYnV0IGFjdHVhbGx5IGBmaWVsZEFgIGlzIGZpcnN0IGluIGl0ZXJhdGlvbiBvcmRlci5cbiAgICAgICAgcmV0dXJuIGZpZWxkO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoXCJ1bnJlYWNoYWJsZVwiKTtcbn07XG5cbi8qKiBVbmNvbnZlbnRpb24gMy4yOiBTbXVnZ2xpbmcgVGhyb3VnaCBTdHJ1Y3R1cmFsIFR5cGluZyAqL1xuY29uc3Qgc3ByZWFkU211Z2dsaW5nID0gPEEsIEIsPihhOiBBLCBiOiBCKTogQiA9PiB7XG4gICAgY29uc3Qgb2JqQSA9IHsgZmllbGQ6IGEgfTtcbiAgICBjb25zdCBvYmo6IHt9ID0gb2JqQTtcbiAgICBjb25zdCBvYmpCID0geyBmaWVsZDogYiwgLi4ub2JqIH07XG4gICAgLy8gYG9iakIuZmllbGRgIGhhcyBiZWVuIG92ZXJ3cml0dGVuIGJ5IHRoZSBzcHJlYWQsXG4gICAgLy8gYnV0IFR5cGVzY3JpcHQgZG9lc24ndCBrbm93IHRoYXQuXG4gICAgcmV0dXJuIG9iakIuZmllbGQ7XG59O1xuXG4vKiogVW5jb252ZW50aW9uIDQ6IGAgfCB2b2lkYCBpcyBWZXJ5IEJhZCAqL1xuY29uc3Qgb3JWb2lkID0gPEEsIEIsPihhOiBBKTogQiA9PiB7XG4gICAgY29uc3Qgb3V0ZXIgPSAoaW5uZXI6ICgpID0+IEIgfCB2b2lkKTogQiA9PiB7XG4gICAgICAgIGNvbnN0IGIgPSBpbm5lcigpO1xuICAgICAgICBpZiAoYikgcmV0dXJuIGI7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcImZhbHN5XCIpO1xuICAgIH07XG5cbiAgICBjb25zdCByZXR1cm5zQSA9ICgpID0+IGE7XG4gICAgY29uc3Qgdm9pZFNtdWdnbGVkOiAoKSA9PiB2b2lkID0gcmV0dXJuc0E7XG4gICAgcmV0dXJuIG91dGVyKHZvaWRTbXVnZ2xlZCk7XG59O1xuIiwib3B0aW9ucyI6eyJydWxlcyI6eyJhY2Nlc3Nvci1wYWlycyI6WyJlcnJvciJdLCJhcnJheS1icmFja2V0LW5ld2xpbmUiOlsiZXJyb3IiXSwiYXJyYXktYnJhY2tldC1zcGFjaW5nIjpbImVycm9yIl0sImFycmF5LWNhbGxiYWNrLXJldHVybiI6WyJlcnJvciJdLCJhcnJheS1lbGVtZW50LW5ld2xpbmUiOlsiZXJyb3IiXSwiYXJyb3ctYm9keS1zdHlsZSI6WyJlcnJvciJdLCJhcnJvdy1wYXJlbnMiOlsiZXJyb3IiXSwiYXJyb3ctc3BhY2luZyI6WyJlcnJvciJdLCJibG9jay1zY29wZWQtdmFyIjpbImVycm9yIl0sImJsb2NrLXNwYWNpbmciOlsiZXJyb3IiXSwiYnJhY2Utc3R5bGUiOlsiZXJyb3IiXSwiY2FsbGJhY2stcmV0dXJuIjpbImVycm9yIl0sImNhbWVsY2FzZSI6WyJlcnJvciJdLCJjYXBpdGFsaXplZC1jb21tZW50cyI6WyJlcnJvciJdLCJjbGFzcy1tZXRob2RzLXVzZS10aGlzIjpbImVycm9yIl0sImNvbW1hLWRhbmdsZSI6WyJlcnJvciJdLCJjb21tYS1zcGFjaW5nIjpbImVycm9yIl0sImNvbW1hLXN0eWxlIjpbImVycm9yIl0sImNvbXBsZXhpdHkiOlsiZXJyb3IiXSwiY29tcHV0ZWQtcHJvcGVydHktc3BhY2luZyI6WyJlcnJvciJdLCJjb25zaXN0ZW50LXJldHVybiI6WyJlcnJvciJdLCJjb25zaXN0ZW50LXRoaXMiOlsiZXJyb3IiXSwiY29uc3RydWN0b3Itc3VwZXIiOlsiZXJyb3IiXSwiY3VybHkiOlsiZXJyb3IiXSwiZGVmYXVsdC1jYXNlIjpbImVycm9yIl0sImRlZmF1bHQtY2FzZS1sYXN0IjpbImVycm9yIl0sImRlZmF1bHQtcGFyYW0tbGFzdCI6WyJlcnJvciJdLCJkb3QtbG9jYXRpb24iOlsiZXJyb3IiXSwiZG90LW5vdGF0aW9uIjpbImVycm9yIl0sImVvbC1sYXN0IjpbImVycm9yIl0sImVxZXFlcSI6WyJlcnJvciJdLCJmb3ItZGlyZWN0aW9uIjpbImVycm9yIl0sImZ1bmMtY2FsbC1zcGFjaW5nIjpbImVycm9yIl0sImZ1bmMtbmFtZS1tYXRjaGluZyI6WyJlcnJvciJdLCJmdW5jLW5hbWVzIjpbImVycm9yIl0sImZ1bmMtc3R5bGUiOlsiZXJyb3IiXSwiZnVuY3Rpb24tY2FsbC1hcmd1bWVudC1uZXdsaW5lIjpbImVycm9yIl0sImZ1bmN0aW9uLXBhcmVuLW5ld2xpbmUiOlsiZXJyb3IiXSwiZ2VuZXJhdG9yLXN0YXItc3BhY2luZyI6WyJlcnJvciJdLCJnZXR0ZXItcmV0dXJuIjpbImVycm9yIl0sImdsb2JhbC1yZXF1aXJlIjpbImVycm9yIl0sImdyb3VwZWQtYWNjZXNzb3ItcGFpcnMiOlsiZXJyb3IiXSwiZ3VhcmQtZm9yLWluIjpbImVycm9yIl0sImhhbmRsZS1jYWxsYmFjay1lcnIiOlsiZXJyb3IiXSwiaWQtYmxhY2tsaXN0IjpbImVycm9yIl0sImlkLWRlbnlsaXN0IjpbImVycm9yIl0sImlkLWxlbmd0aCI6WyJlcnJvciJdLCJpZC1tYXRjaCI6WyJlcnJvciJdLCJpbXBsaWNpdC1hcnJvdy1saW5lYnJlYWsiOlsiZXJyb3IiXSwiaW5kZW50IjpbImVycm9yIl0sImluZGVudC1sZWdhY3kiOlsiZXJyb3IiXSwiaW5pdC1kZWNsYXJhdGlvbnMiOlsiZXJyb3IiXSwianN4LXF1b3RlcyI6WyJlcnJvciJdLCJrZXktc3BhY2luZyI6WyJlcnJvciJdLCJrZXl3b3JkLXNwYWNpbmciOlsiZXJyb3IiXSwibGluZS1jb21tZW50LXBvc2l0aW9uIjpbImVycm9yIl0sImxpbmVicmVhay1zdHlsZSI6WyJlcnJvciJdLCJsaW5lcy1hcm91bmQtY29tbWVudCI6WyJlcnJvciJdLCJsaW5lcy1hcm91bmQtZGlyZWN0aXZlIjpbImVycm9yIl0sImxpbmVzLWJldHdlZW4tY2xhc3MtbWVtYmVycyI6WyJlcnJvciJdLCJsb2dpY2FsLWFzc2lnbm1lbnQtb3BlcmF0b3JzIjpbImVycm9yIl0sIm1heC1jbGFzc2VzLXBlci1maWxlIjpbImVycm9yIl0sIm1heC1kZXB0aCI6WyJlcnJvciJdLCJtYXgtbGVuIjpbImVycm9yIl0sIm1heC1saW5lcyI6WyJlcnJvciJdLCJtYXgtbGluZXMtcGVyLWZ1bmN0aW9uIjpbImVycm9yIl0sIm1heC1uZXN0ZWQtY2FsbGJhY2tzIjpbImVycm9yIl0sIm1heC1wYXJhbXMiOlsiZXJyb3IiXSwibWF4LXN0YXRlbWVudHMiOlsiZXJyb3IiXSwibWF4LXN0YXRlbWVudHMtcGVyLWxpbmUiOlsiZXJyb3IiXSwibXVsdGlsaW5lLWNvbW1lbnQtc3R5bGUiOlsiZXJyb3IiXSwibXVsdGlsaW5lLXRlcm5hcnkiOlsiZXJyb3IiXSwibmV3LWNhcCI6WyJlcnJvciJdLCJuZXctcGFyZW5zIjpbImVycm9yIl0sIm5ld2xpbmUtYWZ0ZXItdmFyIjpbImVycm9yIl0sIm5ld2xpbmUtYmVmb3JlLXJldHVybiI6WyJlcnJvciJdLCJuZXdsaW5lLXBlci1jaGFpbmVkLWNhbGwiOlsiZXJyb3IiXSwibm8tYWxlcnQiOlsiZXJyb3IiXSwibm8tYXJyYXktY29uc3RydWN0b3IiOlsiZXJyb3IiXSwibm8tYXN5bmMtcHJvbWlzZS1leGVjdXRvciI6WyJlcnJvciJdLCJuby1hd2FpdC1pbi1sb29wIjpbImVycm9yIl0sIm5vLWJpdHdpc2UiOlsiZXJyb3IiXSwibm8tYnVmZmVyLWNvbnN0cnVjdG9yIjpbImVycm9yIl0sIm5vLWNhbGxlciI6WyJlcnJvciJdLCJuby1jYXNlLWRlY2xhcmF0aW9ucyI6WyJlcnJvciJdLCJuby1jYXRjaC1zaGFkb3ciOlsiZXJyb3IiXSwibm8tY2xhc3MtYXNzaWduIjpbImVycm9yIl0sIm5vLWNvbXBhcmUtbmVnLXplcm8iOlsiZXJyb3IiXSwibm8tY29uZC1hc3NpZ24iOlsiZXJyb3IiXSwibm8tY29uZnVzaW5nLWFycm93IjpbImVycm9yIl0sIm5vLWNvbnNvbGUiOlsiZXJyb3IiXSwibm8tY29uc3QtYXNzaWduIjpbImVycm9yIl0sIm5vLWNvbnN0YW50LWJpbmFyeS1leHByZXNzaW9uIjpbImVycm9yIl0sIm5vLWNvbnN0YW50LWNvbmRpdGlvbiI6WyJlcnJvciJdLCJuby1jb25zdHJ1Y3Rvci1yZXR1cm4iOlsiZXJyb3IiXSwibm8tY29udGludWUiOlsiZXJyb3IiXSwibm8tY29udHJvbC1yZWdleCI6WyJlcnJvciJdLCJuby1kZWJ1Z2dlciI6WyJlcnJvciJdLCJuby1kZWxldGUtdmFyIjpbImVycm9yIl0sIm5vLWRpdi1yZWdleCI6WyJlcnJvciJdLCJuby1kdXBlLWFyZ3MiOlsiZXJyb3IiXSwibm8tZHVwZS1jbGFzcy1tZW1iZXJzIjpbImVycm9yIl0sIm5vLWR1cGUtZWxzZS1pZiI6WyJlcnJvciJdLCJuby1kdXBlLWtleXMiOlsiZXJyb3IiXSwibm8tZHVwbGljYXRlLWNhc2UiOlsiZXJyb3IiXSwibm8tZHVwbGljYXRlLWltcG9ydHMiOlsiZXJyb3IiXSwibm8tZWxzZS1yZXR1cm4iOlsiZXJyb3IiXSwibm8tZW1wdHkiOlsiZXJyb3IiXSwibm8tZW1wdHktY2hhcmFjdGVyLWNsYXNzIjpbImVycm9yIl0sIm5vLWVtcHR5LWZ1bmN0aW9uIjpbImVycm9yIl0sIm5vLWVtcHR5LXBhdHRlcm4iOlsiZXJyb3IiXSwibm8tZW1wdHktc3RhdGljLWJsb2NrIjpbImVycm9yIl0sIm5vLWVxLW51bGwiOlsiZXJyb3IiXSwibm8tZXZhbCI6WyJlcnJvciJdLCJuby1leC1hc3NpZ24iOlsiZXJyb3IiXSwibm8tZXh0ZW5kLW5hdGl2ZSI6WyJlcnJvciJdLCJuby1leHRyYS1iaW5kIjpbImVycm9yIl0sIm5vLWV4dHJhLWJvb2xlYW4tY2FzdCI6WyJlcnJvciJdLCJuby1leHRyYS1sYWJlbCI6WyJlcnJvciJdLCJuby1leHRyYS1wYXJlbnMiOlsiZXJyb3IiXSwibm8tZXh0cmEtc2VtaSI6WyJlcnJvciJdLCJuby1mYWxsdGhyb3VnaCI6WyJlcnJvciJdLCJuby1mbG9hdGluZy1kZWNpbWFsIjpbImVycm9yIl0sIm5vLWZ1bmMtYXNzaWduIjpbImVycm9yIl0sIm5vLWdsb2JhbC1hc3NpZ24iOlsiZXJyb3IiXSwibm8taW1wbGljaXQtY29lcmNpb24iOlsiZXJyb3IiXSwibm8taW1wbGljaXQtZ2xvYmFscyI6WyJlcnJvciJdLCJuby1pbXBsaWVkLWV2YWwiOlsiZXJyb3IiXSwibm8taW1wb3J0LWFzc2lnbiI6WyJlcnJvciJdLCJuby1pbmxpbmUtY29tbWVudHMiOlsiZXJyb3IiXSwibm8taW5uZXItZGVjbGFyYXRpb25zIjpbImVycm9yIl0sIm5vLWludmFsaWQtcmVnZXhwIjpbImVycm9yIl0sIm5vLWludmFsaWQtdGhpcyI6WyJlcnJvciJdLCJuby1pcnJlZ3VsYXItd2hpdGVzcGFjZSI6WyJlcnJvciJdLCJuby1pdGVyYXRvciI6WyJlcnJvciJdLCJuby1sYWJlbC12YXIiOlsiZXJyb3IiXSwibm8tbGFiZWxzIjpbImVycm9yIl0sIm5vLWxvbmUtYmxvY2tzIjpbImVycm9yIl0sIm5vLWxvbmVseS1pZiI6WyJlcnJvciJdLCJuby1sb29wLWZ1bmMiOlsiZXJyb3IiXSwibm8tbG9zcy1vZi1wcmVjaXNpb24iOlsiZXJyb3IiXSwibm8tbWFnaWMtbnVtYmVycyI6WyJlcnJvciJdLCJuby1taXNsZWFkaW5nLWNoYXJhY3Rlci1jbGFzcyI6WyJlcnJvciJdLCJuby1taXhlZC1vcGVyYXRvcnMiOlsiZXJyb3IiXSwibm8tbWl4ZWQtcmVxdWlyZXMiOlsiZXJyb3IiXSwibm8tbWl4ZWQtc3BhY2VzLWFuZC10YWJzIjpbImVycm9yIl0sIm5vLW11bHRpLWFzc2lnbiI6WyJlcnJvciJdLCJuby1tdWx0aS1zcGFjZXMiOlsiZXJyb3IiXSwibm8tbXVsdGktc3RyIjpbImVycm9yIl0sIm5vLW11bHRpcGxlLWVtcHR5LWxpbmVzIjpbImVycm9yIl0sIm5vLW5hdGl2ZS1yZWFzc2lnbiI6WyJlcnJvciJdLCJuby1uZWdhdGVkLWNvbmRpdGlvbiI6WyJlcnJvciJdLCJuby1uZWdhdGVkLWluLWxocyI6WyJlcnJvciJdLCJuby1uZXN0ZWQtdGVybmFyeSI6WyJlcnJvciJdLCJuby1uZXciOlsiZXJyb3IiXSwibm8tbmV3LWZ1bmMiOlsiZXJyb3IiXSwibm8tbmV3LW5hdGl2ZS1ub25jb25zdHJ1Y3RvciI6WyJlcnJvciJdLCJuby1uZXctb2JqZWN0IjpbImVycm9yIl0sIm5vLW5ldy1yZXF1aXJlIjpbImVycm9yIl0sIm5vLW5ldy1zeW1ib2wiOlsiZXJyb3IiXSwibm8tbmV3LXdyYXBwZXJzIjpbImVycm9yIl0sIm5vLW5vbm9jdGFsLWRlY2ltYWwtZXNjYXBlIjpbImVycm9yIl0sIm5vLW9iai1jYWxscyI6WyJlcnJvciJdLCJuby1vYmplY3QtY29uc3RydWN0b3IiOlsiZXJyb3IiXSwibm8tb2N0YWwiOlsiZXJyb3IiXSwibm8tb2N0YWwtZXNjYXBlIjpbImVycm9yIl0sIm5vLXBhcmFtLXJlYXNzaWduIjpbImVycm9yIl0sIm5vLXBhdGgtY29uY2F0IjpbImVycm9yIl0sIm5vLXBsdXNwbHVzIjpbImVycm9yIl0sIm5vLXByb2Nlc3MtZW52IjpbImVycm9yIl0sIm5vLXByb2Nlc3MtZXhpdCI6WyJlcnJvciJdLCJuby1wcm9taXNlLWV4ZWN1dG9yLXJldHVybiI6WyJlcnJvciJdLCJuby1wcm90byI6WyJlcnJvciJdLCJuby1wcm90b3R5cGUtYnVpbHRpbnMiOlsiZXJyb3IiXSwibm8tcmVkZWNsYXJlIjpbImVycm9yIl0sIm5vLXJlZ2V4LXNwYWNlcyI6WyJlcnJvciJdLCJuby1yZXN0cmljdGVkLWV4cG9ydHMiOlsiZXJyb3IiXSwibm8tcmVzdHJpY3RlZC1nbG9iYWxzIjpbImVycm9yIl0sIm5vLXJlc3RyaWN0ZWQtaW1wb3J0cyI6WyJlcnJvciJdLCJuby1yZXN0cmljdGVkLW1vZHVsZXMiOlsiZXJyb3IiXSwibm8tcmVzdHJpY3RlZC1wcm9wZXJ0aWVzIjpbImVycm9yIl0sIm5vLXJlc3RyaWN0ZWQtc3ludGF4IjpbImVycm9yIl0sIm5vLXJldHVybi1hc3NpZ24iOlsiZXJyb3IiXSwibm8tcmV0dXJuLWF3YWl0IjpbImVycm9yIl0sIm5vLXNjcmlwdC11cmwiOlsiZXJyb3IiXSwibm8tc2VsZi1hc3NpZ24iOlsiZXJyb3IiXSwibm8tc2VsZi1jb21wYXJlIjpbImVycm9yIl0sIm5vLXNlcXVlbmNlcyI6WyJlcnJvciJdLCJuby1zZXR0ZXItcmV0dXJuIjpbImVycm9yIl0sIm5vLXNoYWRvdyI6WyJlcnJvciJdLCJuby1zaGFkb3ctcmVzdHJpY3RlZC1uYW1lcyI6WyJlcnJvciJdLCJuby1zcGFjZWQtZnVuYyI6WyJlcnJvciJdLCJuby1zcGFyc2UtYXJyYXlzIjpbImVycm9yIl0sIm5vLXN5bmMiOlsiZXJyb3IiXSwibm8tdGFicyI6WyJlcnJvciJdLCJuby10ZW1wbGF0ZS1jdXJseS1pbi1zdHJpbmciOlsiZXJyb3IiXSwibm8tdGVybmFyeSI6WyJlcnJvciJdLCJuby10aGlzLWJlZm9yZS1zdXBlciI6WyJlcnJvciJdLCJuby10aHJvdy1saXRlcmFsIjpbImVycm9yIl0sIm5vLXRyYWlsaW5nLXNwYWNlcyI6WyJlcnJvciJdLCJuby11bmFzc2lnbmVkLXZhcnMiOlsiZXJyb3IiXSwibm8tdW5kZWYiOlsiZXJyb3IiXSwibm8tdW5kZWYtaW5pdCI6WyJlcnJvciJdLCJuby11bmRlZmluZWQiOlsiZXJyb3IiXSwibm8tdW5kZXJzY29yZS1kYW5nbGUiOlsiZXJyb3IiXSwibm8tdW5leHBlY3RlZC1tdWx0aWxpbmUiOlsiZXJyb3IiXSwibm8tdW5tb2RpZmllZC1sb29wLWNvbmRpdGlvbiI6WyJlcnJvciJdLCJuby11bm5lZWRlZC10ZXJuYXJ5IjpbImVycm9yIl0sIm5vLXVucmVhY2hhYmxlIjpbImVycm9yIl0sIm5vLXVucmVhY2hhYmxlLWxvb3AiOlsiZXJyb3IiXSwibm8tdW5zYWZlLWZpbmFsbHkiOlsiZXJyb3IiXSwibm8tdW5zYWZlLW5lZ2F0aW9uIjpbImVycm9yIl0sIm5vLXVuc2FmZS1vcHRpb25hbC1jaGFpbmluZyI6WyJlcnJvciJdLCJuby11bnVzZWQtZXhwcmVzc2lvbnMiOlsiZXJyb3IiXSwibm8tdW51c2VkLWxhYmVscyI6WyJlcnJvciJdLCJuby11bnVzZWQtcHJpdmF0ZS1jbGFzcy1tZW1iZXJzIjpbImVycm9yIl0sIm5vLXVudXNlZC12YXJzIjpbImVycm9yIl0sIm5vLXVzZS1iZWZvcmUtZGVmaW5lIjpbImVycm9yIl0sIm5vLXVzZWxlc3MtYXNzaWdubWVudCI6WyJlcnJvciJdLCJuby11c2VsZXNzLWJhY2tyZWZlcmVuY2UiOlsiZXJyb3IiXSwibm8tdXNlbGVzcy1jYWxsIjpbImVycm9yIl0sIm5vLXVzZWxlc3MtY2F0Y2giOlsiZXJyb3IiXSwibm8tdXNlbGVzcy1jb21wdXRlZC1rZXkiOlsiZXJyb3IiXSwibm8tdXNlbGVzcy1jb25jYXQiOlsiZXJyb3IiXSwibm8tdXNlbGVzcy1jb25zdHJ1Y3RvciI6WyJlcnJvciJdLCJuby11c2VsZXNzLWVzY2FwZSI6WyJlcnJvciJdLCJuby11c2VsZXNzLXJlbmFtZSI6WyJlcnJvciJdLCJuby11c2VsZXNzLXJldHVybiI6WyJlcnJvciJdLCJuby12YXIiOlsiZXJyb3IiXSwibm8tdm9pZCI6WyJlcnJvciJdLCJuby13YXJuaW5nLWNvbW1lbnRzIjpbImVycm9yIl0sIm5vLXdoaXRlc3BhY2UtYmVmb3JlLXByb3BlcnR5IjpbImVycm9yIl0sIm5vLXdpdGgiOlsiZXJyb3IiXSwibm9uYmxvY2stc3RhdGVtZW50LWJvZHktcG9zaXRpb24iOlsiZXJyb3IiXSwib2JqZWN0LWN1cmx5LW5ld2xpbmUiOlsiZXJyb3IiXSwib2JqZWN0LWN1cmx5LXNwYWNpbmciOlsiZXJyb3IiXSwib2JqZWN0LXByb3BlcnR5LW5ld2xpbmUiOlsiZXJyb3IiXSwib2JqZWN0LXNob3J0aGFuZCI6WyJlcnJvciJdLCJvbmUtdmFyIjpbImVycm9yIl0sIm9uZS12YXItZGVjbGFyYXRpb24tcGVyLWxpbmUiOlsiZXJyb3IiXSwib3BlcmF0b3ItYXNzaWdubWVudCI6WyJlcnJvciJdLCJvcGVyYXRvci1saW5lYnJlYWsiOlsiZXJyb3IiXSwicGFkZGVkLWJsb2NrcyI6WyJlcnJvciJdLCJwYWRkaW5nLWxpbmUtYmV0d2Vlbi1zdGF0ZW1lbnRzIjpbImVycm9yIl0sInByZWZlci1hcnJvdy1jYWxsYmFjayI6WyJlcnJvciJdLCJwcmVmZXItY29uc3QiOlsiZXJyb3IiXSwicHJlZmVyLWRlc3RydWN0dXJpbmciOlsiZXJyb3IiXSwicHJlZmVyLWV4cG9uZW50aWF0aW9uLW9wZXJhdG9yIjpbImVycm9yIl0sInByZWZlci1uYW1lZC1jYXB0dXJlLWdyb3VwIjpbImVycm9yIl0sInByZWZlci1udW1lcmljLWxpdGVyYWxzIjpbImVycm9yIl0sInByZWZlci1vYmplY3QtaGFzLW93biI6WyJlcnJvciJdLCJwcmVmZXItb2JqZWN0LXNwcmVhZCI6WyJlcnJvciJdLCJwcmVmZXItcHJvbWlzZS1yZWplY3QtZXJyb3JzIjpbImVycm9yIl0sInByZWZlci1yZWZsZWN0IjpbImVycm9yIl0sInByZWZlci1yZWdleC1saXRlcmFscyI6WyJlcnJvciJdLCJwcmVmZXItcmVzdC1wYXJhbXMiOlsiZXJyb3IiXSwicHJlZmVyLXNwcmVhZCI6WyJlcnJvciJdLCJwcmVmZXItdGVtcGxhdGUiOlsiZXJyb3IiXSwicHJlc2VydmUtY2F1Z2h0LWVycm9yIjpbImVycm9yIl0sInF1b3RlLXByb3BzIjpbImVycm9yIl0sInF1b3RlcyI6WyJlcnJvciJdLCJyYWRpeCI6WyJlcnJvciJdLCJyZXF1aXJlLWF0b21pYy11cGRhdGVzIjpbImVycm9yIl0sInJlcXVpcmUtYXdhaXQiOlsiZXJyb3IiXSwicmVxdWlyZS11bmljb2RlLXJlZ2V4cCI6WyJlcnJvciJdLCJyZXF1aXJlLXlpZWxkIjpbImVycm9yIl0sInJlc3Qtc3ByZWFkLXNwYWNpbmciOlsiZXJyb3IiXSwic2VtaSI6WyJlcnJvciJdLCJzZW1pLXNwYWNpbmciOlsiZXJyb3IiXSwic2VtaS1zdHlsZSI6WyJlcnJvciJdLCJzb3J0LWltcG9ydHMiOlsiZXJyb3IiXSwic29ydC1rZXlzIjpbImVycm9yIl0sInNvcnQtdmFycyI6WyJlcnJvciJdLCJzcGFjZS1iZWZvcmUtYmxvY2tzIjpbImVycm9yIl0sInNwYWNlLWJlZm9yZS1mdW5jdGlvbi1wYXJlbiI6WyJlcnJvciJdLCJzcGFjZS1pbi1wYXJlbnMiOlsiZXJyb3IiXSwic3BhY2UtaW5maXgtb3BzIjpbImVycm9yIl0sInNwYWNlLXVuYXJ5LW9wcyI6WyJlcnJvciJdLCJzcGFjZWQtY29tbWVudCI6WyJlcnJvciJdLCJzdHJpY3QiOlsiZXJyb3IiXSwic3dpdGNoLWNvbG9uLXNwYWNpbmciOlsiZXJyb3IiXSwic3ltYm9sLWRlc2NyaXB0aW9uIjpbImVycm9yIl0sInRlbXBsYXRlLWN1cmx5LXNwYWNpbmciOlsiZXJyb3IiXSwidGVtcGxhdGUtdGFnLXNwYWNpbmciOlsiZXJyb3IiXSwidW5pY29kZS1ib20iOlsiZXJyb3IiXSwidXNlLWlzbmFuIjpbImVycm9yIl0sInZhbGlkLXR5cGVvZiI6WyJlcnJvciJdLCJ2YXJzLW9uLXRvcCI6WyJlcnJvciJdLCJ3cmFwLWlpZmUiOlsiZXJyb3IiXSwid3JhcC1yZWdleCI6WyJlcnJvciJdLCJ5aWVsZC1zdGFyLXNwYWNpbmciOlsiZXJyb3IiXSwieW9kYSI6WyJlcnJvciJdfSwibGFuZ3VhZ2VPcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYUZlYXR1cmVzIjp7ImpzeCI6dHJ1ZX0sInNvdXJjZVR5cGUiOiJtb2R1bGUifSwicGFyc2VyIjoiQHR5cGVzY3JpcHQtZXNsaW50L3BhcnNlciJ9fX0=">eslint playground</a> <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-eslint>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Some New Albums With A Solid Beat (#8)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-10-01-some-new-albums-with-a-solid-beat-8-/" />
      <id>https://wolfgirl.dev/blog/2025-10-01-some-new-albums-with-a-solid-beat-8-/</id>
      <published>2025-10-01T17:03:27Z</published>
      <updated>2025-10-01T17:03:27Z</updated>
      <summary><![CDATA[Bandcamp Friday is coming up again, so you know what that means!! More album reviews :) Ninajirachi – I Love My Computer Man. I was extre...]]></summary>
      <content type="html"><![CDATA[<p><a href=https://daily.bandcamp.com/features/bandcamp-fridays>Bandcamp Friday is coming up again</a>, so you know what that means!! More album reviews :)</p><h2><a aria-hidden=true class=anchor href=#ninajirachi--i-love-my-computer id=heading-ninajirachi--i-love-my-computer></a><a href=https://ninajirachi.bandcamp.com/album/i-love-my-computer>Ninajirachi – I Love My Computer</a></h2><p><img alt="Ninajirachi is surrounded by all sorts of computer equipment & wires in a chaotically-organized room" src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/i-love-my-computer.jpg></p><p>Man. I was extremely hyped for this one after listening to Girl EDM (covered in <a href=https://wolfgirl.dev/blog/2025-08-01-purchases-i-absolutely-must-make-this-bandcamp-friday-7-/>issue #7</a>), and not only did this not disappoint, it completely blew me out of the water & is now my favorite thing put out by Ninajirachi.</p><p>This is a hyper-modern kind of EDM, very much the kind that can only be made on a computer, which this album is absolutely reverent of. It's also about Nina's relationship with computers & making music, most obviously in tracks like "F**k My Computer" & "Sing Good", but really this theme is present in all tracks.</p><h3><a aria-hidden=true class=anchor href=#favorite-track-04--csirac id=heading-favorite-track-04--csirac></a>Favorite track: 04 – CSIRAC</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/04_CSIRAC.mp3></audio></p><p>"All I Am", "All At Once", and "Sing Good" are all really good as well, but are sort of <em>too</em> emotional for specific moods. CSIRAC is my overall favorite because it's a pumping bop no matter what!</p><h2><a aria-hidden=true class=anchor href=#various-artists--stream-palette-5--ranked- id=heading-various-artists--stream-palette-5--ranked-></a><a href=https://diversesystem.bandcamp.com/album/stream-palette-5-ranked>Various Artists – Stream Palette 5 -Ranked-</a></h2><p><img alt="Both the anime Stream Palette mascots sit in front of gaming battlestations" src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/stream-palette-5-ranked.jpg></p><p>Stumbled upon this by a chance algorithmic recommendation and yeah, I do love Japanese music, thanks for noticing! It's got all the big names like Zekk, Nhato, KO3, & more, with a corresponding diversity of genres like trance, phonk, color bass, & of course, dubstep:</p><h3><a aria-hidden=true class=anchor href=#favorite-track-08--nhato--killer-in-the-mirror id=heading-favorite-track-08--nhato--killer-in-the-mirror></a>Favorite track: 08 – Nhato – Killer in the Mirror</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/08_Killer_in_the_Mirror.mp3></audio></p><p>This being the 5th in a series does make me want to go back for more. The "-Ranked-" part seems to mean that this is the more hardcore version, as compared to "-Chatting-" which is more chill; wonder how that mix works on the previous, non-split albums? anyways</p><h2><a aria-hidden=true class=anchor href=#iglooghost--bronze-claw-iso id=heading-iglooghost--bronze-claw-iso></a><a href=https://iglooghost.bandcamp.com/album/bronze-claw-iso-1>Iglooghost – Bronze Claw Iso</a></h2><p><img alt="A construction worker stands in front of an excavator, its claw looming over his shoulder. They are in the middle of a desolate wasteland, everything color-graded a muddy brown" src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/bronze-claw-iso.jpg></p><p>With Iglooghost, you always know you're getting cool new experimental music, complete with tons of backing lore, this time at <a href=https://neurowaste.net>https://neurowaste.net</a>. True to the theme, this one is a lot more industrial-sounding than past albums like Lei Line Eon or Tidal Memory Exo.</p><p>Because the sounds are so experimental, they're always a bit hit-or-miss for me, but there's usually a banger tucked away, like Slug Song. Not to insinuate the other tracks aren't good (of course they are), just that this one song gets stuck in my head quite often & doesn't leave.</p><h3><a aria-hidden=true class=anchor href=#favorite-track-04--slug-song id=heading-favorite-track-04--slug-song></a>Favorite track: 04 – Slug Song</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/04_Slug_Song.mp3></audio></p><h2><a aria-hidden=true class=anchor href=#moe-shop--evo-evo id=heading-moe-shop--evo-evo></a><a href=https://moeshop.bandcamp.com/album/evo-evo>Moe Shop – EVO EVO</a></h2><p><img alt='An anime girl with piercing eyes, red hair, and tons of stickers on her including a "Moe Shop EVO EVO" one, is in front of an abstract blue background' src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/evo-evo.jpg title=title></p><p>New classic Moe Shop! Their last full album release was <a href=https://moeshop.bandcamp.com/album/moe-moe>Moe Moe</a>, all the way back in March 2018!! They've certainly kept busy since then, with a steady stream of collabs with other artists, commercial commissions, & DJ sets over the years (as per their website, <a href=https://www.moeshop.jp>https://www.moeshop.jp</a>). Still, it sure is nice to have something full-length to chew on :)</p><h3><a aria-hidden=true class=anchor href=#favorite-track-02--complications id=heading-favorite-track-02--complications></a>Favorite track: 02 – COMPLICATIONS</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/02_COMPLICATIONS.mp3></audio></p><p>Like I said, "new classic Moe Shop", very distinctive flavor of music they have :P Of course, this album also includes a bunch of collabs, including one with everyone's favorite Cohost artist:</p><h3><a aria-hidden=true class=anchor href=#07--newly-human-feeling-feat-jamie-paige id=heading-07--newly-human-feeling-feat-jamie-paige></a>07 – NEWLY HUMAN FEELING (feat. Jamie Paige)</h3><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/0199a0a6-d94b-7447-b162-0e5be1cbfffc/07_NEWLY_HUMAN_FEELING.mp3></audio></p><hr><p>Lastly, I do want to give another shoutout to an album that I've been listening to a bunch again, <a href=https://tangermusic.bandcamp.com/album/prefer-not-to-say>Tanger – Prefer not to say</a>. Can't believe it's almost been a full year... Anyways, that's it for this issue, happy spooky pumpkin season!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Making Of: Yes-and-ism]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-09-27-making-of-yes-and-ism/" />
      <id>https://wolfgirl.dev/blog/2025-09-27-making-of-yes-and-ism/</id>
      <published>2025-09-28T00:58:43Z</published>
      <updated>2025-09-28T00:58:43Z</updated>
      <summary><![CDATA[This is the "[long explanation]" part of the Yes-and-ism photography collection I put out yesterday. Hopefully this demystifies some of t...]]></summary>
      <content type="html"><![CDATA[<p>This is the "[long explanation]" part of the <a href=https://wolfgirl.dev/blog/2025-09-26-coho-photo-zine-yes-and-ism/>Yes-and-ism photography collection</a> I put out yesterday. Hopefully this demystifies some of the stuff I did and inspires some more "glitch art" too!</p><h2><a aria-hidden=true class=anchor href=#step-1-find-images id=heading-step-1-find-images></a>Step 1: Find Images</h2><p>There's no getting around this: to start, I had to take a <em>lot</em> of photos just to sift through them all in order to come up with (only!) 9 final pieces. Each step is a whittling-down: from "everything" to "just stuff that immediately looks interesting" to "stuff that's probably going to work" to "stuff that actually does work". Let's see an image that made it all the way, image #1, <code>IMG_0540.HEIC</code><sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> ("antennae"):</p><p><img title="Stage 0" alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage0.png></p><h2><a aria-hidden=true class=anchor href=#step-2-look-for-interesting-encodings id=heading-step-2-look-for-interesting-encodings></a>Step 2: Look For Interesting Encodings</h2><p>My goal for this project was "make something that looks good even after being run thru a <code>-quality 1</code> encoder". In order to make that happen, I first had to know how each encoder behaved. I had in mind 4 lossy image formats: <a href=https://en.wikipedia.org/wiki/AVIF>AVIF</a>, <a href=https://en.wikipedia.org/wiki/JPEG>JPEG</a>, <a href=https://en.wikipedia.org/wiki/JPEG_XL>JPEG XL</a>, and <a href=https://en.wikipedia.org/wiki/WebP>WebP</a>. There are other lossy formats out there, but these I already knew had "interesting" behavior at low quality levels & were readily understood by all my image manipulation tools.</p><p>To make these, I used <a href=https://imagemagick.org/>ImageMagick</a> (and a POSIX shell):</p><pre class=syntax-highlighting><code class=language-sh><span class=keyword>for</span> <span class=property>enc</span> <span class=keyword>in</span> avif jpeg jxl webp; <span class=keyword>do</span>
    <span class=function>magick</span> <span class=constant>-quality</span> 1 IMG_0540_stage0.png IMG_0540_stage1.<span class=embedded>${</span><span class=property>enc</span><span class=embedded>}</span>
    <span class=function>magick</span> IMG_0540_stage1.<span class=embedded>${</span><span class=property>enc</span><span class=embedded>}</span> IMG_0540_stage1_<span class=embedded>${</span><span class=property>enc</span><span class=embedded>}</span>.png
<span class=keyword>done</span></code></pre><div class=table-wrapper><table><tbody><tr><td><figure><p><img title="Stage 1 AVIF" alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage1_avif.png></p><figcaption>AVIF</figcaption></figure></td><td><figure><p><img title="Stage 1 JPEG" alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage1_jpeg.png></p><figcaption>JPEG</figcaption></figure></td></tr><tr><td><figure><p><img title="Stage 1 JPEG XL" alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage1_jxl.png></p><figcaption>JPEG XL</figcaption></figure></td><td><figure><p><img title="Stage 1 WebP" alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage1_webp.png></p><figcaption>WebP</figcaption></figure></td></tr></tbody></table></div><p>Right away, we see that JPEG (the oldest format) is a huge outlier; compared to the other formats, it's done <em>crazy</em> things to the colors & shapes, very limited palette & lots of hard edges. Quite distinctive, but maybe a bit <em>too</em> crazy... and also too recognizable, everyone & their mom knows what a deep fried JPEG looks like nowadays.</p><p>AVIF is second when it comes to aesthetics. It does a little bit of fun stuff with the colors still, especially with that prominent line down the middle, and generally makes things more blocky too. JPEG XL is third, making things look blockier if you zoom in but not from a distance. WebP is dead last, everything just gets blurrier in a boring way.</p><p>Here are the resulting filesizes<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>:</p><pre class=syntax-highlighting><code>$ eza -la IMG_0540_stage*
.rw-rw-r-- 9.7M me 27 Sep 16:53 IMG_0540_stage0.png
.rw-rw-r--  22k me 27 Sep 16:55 IMG_0540_stage1.avif
.rw-rw-r--  96k me 27 Sep 16:55 IMG_0540_stage1.jpeg
.rw-rw-r--  68k me 27 Sep 16:55 IMG_0540_stage1.jxl
.rw-rw-r-- 107k me 27 Sep 16:55 IMG_0540_stage1.webp
.rw-rw-r--  24M me 27 Sep 16:57 IMG_0540_stage1_avif.png
.rw-rw-r--  12M me 27 Sep 16:57 IMG_0540_stage1_jpeg.png
.rw-rw-r--  28M me 27 Sep 16:57 IMG_0540_stage1_jxl.png
.rw-rw-r--  28M me 27 Sep 16:57 IMG_0540_stage1_webp.png
</code></pre><p>It's interesting to me that JPEG has the second largest filesize when it's also the most unnatural looking; I guess modern image formats really are improved huh :P. It's also interesting to see the filesizes of the resulting PNGs after the other compression passes. "Don't put a zip inside a zip" definitely applies to images, though some fault in the large size might also be due to ImageMagick's default settings. Anyways.</p><h2><a aria-hidden=true class=anchor href=#step-3-run-the-pipeline id=heading-step-3-run-the-pipeline></a>Step 3: Run The Pipeline</h2><p>After seeing these encoding results on various images, I somewhat arbitrarily decided on the following pipeline:</p><ol><li>Encode as JPEG</li><li>Blend with 50% of step 1, 50% of original</li><li>Encode as AVIF</li><li>Blend with 75% of step 3, 25% of step 2</li><li>Encode a JPEG XL</li></ol><p>Expressing this as a Makefile:</p><pre class=syntax-highlighting><code class=language-make>input_files := $(shell find -type f -name "*.HEIC")
output_files := $(input_files:%.HEIC=%_stage3.png)
all: $(output_files)
# (1)
%_stage1.jpg: %.HEIC
	magick -quality 1 $&lt; $@
# (2)
%_stage1_blend.txt: %_stage1.jpg
	python3 -c 'print(input(""),end="")' > $@
%_stage1.png: %_stage1_blend.txt %_stage1.jpg %.HEIC
	magick composite -blend $(shell cat $&lt;) $(filter-out $&lt;,$^) $@
# (3)
%_stage2.avif: %_stage1.png
	magick -quality 1 $&lt; $@
# (4)
%_stage2_blend.txt: %_stage2.avif
	python3 -c 'print(input(""),end="")' > $@
%_stage2.png: %_stage2_blend.txt %_stage2.avif %_stage1.png
	magick composite -blend $(shell cat $&lt;) $(filter-out $&lt;,$^) $@
# (5)
%_stage3.jxl: %_stage2.png # (5)
	magick -quality 1 $&lt; $@
%_stage3.png: %_stage3.jxl
	magick $&lt; $@
.SECONDARY: # Keep all intermediate files
</code></pre><p>Each <code>*_blend.txt</code> step was so that I could interactively choose a blending amount for each image, because while 50% & 75% were pretty ok most the time, they were not universally good. As an example, <code>IMG_0540</code> needed 70% blend on the first stage, otherwise the blockiness didn't show up enough for my tastes. The benefit of using a Makefile like this is that, after seeing a result I didn't like at 50, I could always update it with <code>echo -n "70" > IMG_0540_stage1_blend.txt; make</code>.</p><p>I made sure to record all the values used to make every image, but it's too boring to reproduce them all here, so I'll show off just the one completed pipeline instead:</p><div class=table-wrapper><table><tbody><tr><td><figure><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage1_jpeg.png></p><figcaption>Stage 1, no blend</figcaption></figure></td><td><figure><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage1.png></p><figcaption>Stage 1, <code>-blend 70</code></figcaption></figure></td></tr><tr><td><figure><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage2_avif.png></p><figcaption>Stage 2, no blend</figcaption></figure></td><td><figure><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage2.png></p><figcaption>Stage 2, <code>-blend 75</code></figcaption></figure></td></tr><tr><td colspan=2><figure><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/IMG_0540_stage3.png></p><figcaption>Stage 3</figcaption></figure></td></tr></tbody></table></div><h2><a aria-hidden=true class=anchor href=#step-4-postprocessing id=heading-step-4-postprocessing></a>Step 4: Postprocessing</h2><p>Now, you'll notice that there are no super blocky artifacts in this stage 3 image like there are in the final result I posted elsewhere. This is because I fooled you!! I made things <em>look</em> similar to compression artifacats, when actually it's just a specifically placed HSV Saturation mask designed to blow out the colors in ways that interact strangely with existing HEIC compression artifacts :3</p><div style="grid-template-columns:3fr 7fr;gap:16px;display:grid"><p><img alt="Screenshot of the GIMP layer view confirming the above statement" title="ohoho what a fun prank i pulled" src=https://static.wolfgirl.dev/polywolf/blog/01998ce9-206a-722c-b373-ab8c750a467d/Screenshot_20250927_174704.png></p><p><img alt="Small cellular and radio antennae are blocked out in high saturation against a gray sky" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0540-export.PNG title=antennae></p></div><p>I used postprocessing tricks on other images too, like the Colors > Desaturate > Mono Mixer tool, with "preserve luminosity" turned on & one of the channels having negative weight, for slightly wild effects on #3 ("split sky") & #5 ("undergrowth"). For the last one ("sunset"), I made it so there was a gradient mask smoothly transitioning between the original image in the bottom left to the compressed version in the upper right, following the overall gradient of the sky.</p><p>Overall!! There's a lot of fun to be had in messing around with images digitally, this really is just the tip of the iceberg if I want to <em>actually</em> start doing glitch art. I certainly enjoyed myself a lot with this and hope to do more as a part of <a href=https://coho.photo/>The Coho Photozine Club</a>!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Yes, these are all Shot on iPhone™️, I have another, smaller backlog of things on a "proper" digital camera that I did not have enough time to edit for these unfortunately. Canon raws look pretty good but they always can use a <em>little</em> bit of tweaking, whereas iPhone photos always pop. "just shoot jpeg" i guess <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>In order to not destroy any devices that load this page with 28MB images near the top of the fold, my website pipeline automatically compresses them to <code>-quality 90</code> WebP at various sizes. If you click on any given image (left-click, not right-click), you'll be taken to the original for closer inspection. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Coho Photo Zine – Yes-and-ism]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-09-26-coho-photo-zine-yes-and-ism/" />
      <id>https://wolfgirl.dev/blog/2025-09-26-coho-photo-zine-yes-and-ism/</id>
      <published>2025-09-27T03:21:29Z</published>
      <updated>2025-09-27T03:21:29Z</updated>
      <summary><![CDATA[Recently, I found out about the [Coho Photo Zine](https://coho.photo/), a zine run by former members of the Cohost photography community...]]></summary>
      <content type="html"><![CDATA[<p>Recently, I found out about the <a href=https://coho.photo/>Coho Photo Zine</a>, a zine run by former members of the Cohost photography community whom I really look up to. By "recently" I mean "Tuesday", and their deadline was today (Friday), so I had about 3 days to sort thru my backlog and edit something matching the theme. And I did!! Having some creative pressure was actually very helpful, as was seeing so many inspiring photos by all the other participants.</p><p>The zine is not coming out for a while, and there's no guarantee any of these photos will be in it, so I figured I'd just post them now :3</p><hr><p>The theme for the inaugural zine issue is "yes-and-ism". As per one of the organizers:</p><blockquote><p>it comes from someone asking "is that__?" and the response being "yes, and [long explanation]". it was a unique aspect to cohost style photography that most of us haven't found a place for outside of cohost or the various life raft servers, where the focus was less on the actual image but more on the process of image making and the conversations said processes inspired.</p></blockquote><p>There's more themes from the <a href=https://coho.photo/primer>primer</a> that I tried to incorporate too, like orthorealism + deliberate imperfection, with my overall thrust being "what would look cool after being passed thru a <code>-quality 1</code> encoder?" The end results are better than anything I could have asked for.</p><br><br><br><br><p><img alt="Small cellular and radio antennae are blocked out in high saturation against a gray sky" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0540-export.PNG title=antennae></p><br><br><br><br><p><img alt="Straight-on view of a brownstone rooftop against a slightly taller gray brick building and a blocky sky" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0780.PNG title=rooftop></p><br><br><br><br><p><img alt="A slight crack in the sky separates poster-shaded monochrome apartment buildings" title="split sky" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0806-export.PNG></p><br><br><br><br><p><img alt="Various monochrome pipes of all different sizes line the ceiling in orderly patterns" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0517-export.PNG title=pipes></p><br><br><br><br><p><img alt="The ferns underneath a tower supporting a bridge have become unnaturally shaped & colored due to the compression" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0369-export.PNG title=undergrowth></p><br><br><br><br><p><img alt="Sickly yellow warped windowpanes line flanking office buildings, themselves banded against a banding black night sky" title="office buildings" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0735_stage2.png></p><br><br><br><br><p><img alt="An excavator, light SUV, and graffiti are all lightly touched by compression artifacts, the colors unnaturally saturated" title="parked vehicles" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0330.PNG></p><br><br><br><br><p><img alt="Square green highlights for red algae buildup on calm gray-brown water, rippling out toward the bottom of the frame" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0365.PNG title=canal></p><br><br><br><br><p><img alt="A pink and blue sunset gives way to highly compressed dark gray clouds" src=https://static.wolfgirl.dev/polywolf/blog/0199890b-86f7-7336-8574-f85f9bdf76f0/IMG_0663-export.PNG title=sunset></p><br><br><br><br><hr><p>I'll have a making-of post coming soon hopefully :)</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Per-Tag RSS Feeds]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-08-18-per-tag-rss-feeds/" />
      <id>https://wolfgirl.dev/blog/2025-08-18-per-tag-rss-feeds/</id>
      <published>2025-08-19T02:38:30Z</published>
      <updated>2025-08-19T02:38:30Z</updated>
      <summary><![CDATA[Just a small site update! For anyone who was thinking "PolyWolf is so cool & awesome buuut she makes a lot of non-programming blog posts,...]]></summary>
      <content type="html"><![CDATA[<p>Just a small site update! For anyone who was thinking "PolyWolf is so cool & awesome buuut she makes a lot of non-programming blog posts, i just want to see the <a href=https://wolfgirl.dev/tags/programming/>#programming</a> ones because those are super high-quality". Or if not exactly that thought then something similar, perhaps about another tag. In any case! ur in luck u hypothetical being from the future, i have that now :3</p><p>Put <a href=https://wolfgirl.dev/tags/programming/rss.xml>https://wolfgirl.dev/tags/programming/rss.xml</a> (or any other tag) in your feed reader, or just point it at the <a href=https://wolfgirl.dev/tags/programming/>https://wolfgirl.dev/tags/programming/</a> page directly b/c I'm pretty sure the metadata is set up correctly there too. Adds a couple more seconds to my build time but eh totally worth it.</p><p>Also wow I cannot take even 1 hour for simple side-project without <a href=https://github.com/withastro/astro/issues/11447#issuecomment-3198981545>running into another bug</a>. I was doing a non-NixOS thing to get away from all the weird bugs!! Fortunately it is just "the URL being rendered in dev is <code>/tags/programming/rss.xml/</code>, which is different from the prod URL", not a hard blocker, but it does make my brain itch a little with the urge to fix it. No. I must resist... Too many other side-projects r crying out to be finished too...</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Shin Godzilla (2016 film)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-08-16-shin-godzilla-2016-film-/" />
      <id>https://wolfgirl.dev/blog/2025-08-16-shin-godzilla-2016-film-/</id>
      <published>2025-08-17T02:10:32Z</published>
      <updated>2025-08-17T02:10:32Z</updated>
      <summary><![CDATA[Hideki Ano is the **man** i tell ya hwat. Just saw this in theaters w/ a friend because the local theater had it on, and it was a blast....]]></summary>
      <content type="html"><![CDATA[<p>Hideki Ano is the <strong>man</strong> i tell ya hwat. Just saw this in theaters w/ a friend because the local theater had it on, and it was a blast. Very reminiscent of Evangelion; most obviously "there is a big monster and we (the government) have to take it down", but also:</p><ul><li>The ways in which the monster is used as a prop to trigger the real juicy political/emotional conflicts</li><li>Lingering shots of machinery/infrastructure/crowds of people to get a grounded sense of what's going on, and demonstrate that these operations are more than just one person imposing their will, but a collective organizing towards a common goal</li><li>The "don, don, don, don don-don" music that plays whenever there is a tense scenario (score in general seemed very similar i think!)</li><li>Misato for president of the united states of america</li></ul><p>I can see why it was a flop with foreign audiences: the CG is real bad, and the movie mostly consists of people talking about things rather than directly fighting the big monster.</p><p>I can also see why it was critically acclaimed in Japan: very simple, positive message about the tenacity of the Japanese people in times of crisis, if only these darn old politicians would stop ruining everything.</p><p><img alt="Shin Godzilla Japanese theatrical release poster" src=https://static.wolfgirl.dev/polywolf/blog/0198b5b0-b607-7bb4-b709-4086a8fcd4e1/IMG_0584.jpeg></p><p>i rate it "hit Godzilla with a skyscraper"/5. highly recommend if you like Eva 1.0</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I Am Almost (But Not Quite) Ready To Switch To NixOS]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-08-05-i-am-almost-but-not-quite-ready-to-switch-to-nixos/" />
      <id>https://wolfgirl.dev/blog/2025-08-05-i-am-almost-but-not-quite-ready-to-switch-to-nixos/</id>
      <published>2025-08-06T03:27:49Z</published>
      <updated>2025-08-06T03:27:49Z</updated>
      <summary><![CDATA[Feels like 9000 years, but really it's only been 4 months give or take. The reason it's taken this long is partly my own fault, and partl...]]></summary>
      <content type="html"><![CDATA[<p>Feels like 9000 years, but really it's only been 4 months give or take. The reason it's taken this long is partly my own fault, and partly the fault of Nix itself.</p><p>You see, I like to <a href=https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/>write my own tools</a>. What that ends up meaning is, in addition to running some "standard" services like a FreshRSS server & a Bluesky PDS on my cloud box, I also run some very non-standard stuff like <a href=https://girl.technology/>https://girl.technology/</a>, <a href=https://wolfgirl.dev/blog/2024-11-08-what-if-anyone-could-use-my-post-composer-/>my post composer</a>, & a small suite of home-grown discord bots. The "standard" services are all nicely packaged thanks to the hard work of many volunteers, but for my own stuff, I <em>have</em> to write my own NixOS modules if i want to run them on NixOS.</p><p>Fortunately, writing custom NixOS modules isn't too challenging once you get a proper setup going<sup class=footnote-ref><a data-footnote-ref href=#fn-getting-a-proper-setup id=fnref-getting-a-proper-setup>1</a></sup>. Unfortunately for present me, past me did not really know about Nix when designing some tools, making assumptions like "of course we will be able to write to the directory my Python code lives in" & "of course <code>$PATH</code> will always be populated with all the programs we could possibly want". <a href=https://girl.technology/>https://girl.technology/</a>, being a Rust project where I tried to follow as many best practices as possible (including packaging in a flake!), was thankfully spared most of this, but my post composer & discord bots... first i took a month to polish up a blog post detailing <a href=https://wolfgirl.dev/blog/2025-05-15-packaging-multiple-dependent-python-modules-using-hatch-nix/>exactly how i was packaging my Python code</a>, just to make sure i understood everything that was going on, <em>then</em> i had to <a href=https://wolfgirl.dev/blog/2025-06-07-i-m-a-nixpkgs-maintainer-now/>become a nixpkgs maintainer</a> because some Python code upstream was broken (another month or so), and only this month did i realize "wait a minute i haven't actually fixed any of the bugs in my own code yet". so i did that. and hoo momma there were lots</p><p>No particular standouts really, just the standard "ah one more <code>substititeInPlace</code>/<code>wrapProgram</code>/dependency for <code>patchShebangsAuto</code>", plus figuring out how to detangle "everything goes into <code>.</code>" into "everything goes into<code>$DATA_DIR</code>"<sup class=footnote-ref><a data-footnote-ref href=#fn-on-standard-stuff id=fnref-on-standard-stuff>2</a></sup>. That and converting a hand-written nginx config to a NixOS nginx module definition is worthy of exactly the time I allotted it (couple hours on a weekend), and yet, uh, just never got around to it 'til now ig 🙃</p><p>Finnicky & annoying & <em>feels</em> bad b/c I already have a perfectly cromulent Ubuntu 24.04 LTS server hosting all my stuff already, and it's been happily chugging along with no complaints. Just a few problems:</p><ol><li>Every time the server reboots I have to ssh in and start a <code>tmux</code> session because all my services run as systemd user services that only run when my user has a session, not on boot.</li><li>Dist upgrades are liable to break all the Python virtual environments I have set up all over the place b/c those have links back to the main Python install.</li></ol><p>Now, was it worth spending all this time just to sand off these few rough edges? Probably not! The one weekend I spent porting Python packages was likely more time than I would spend in the next 2 years managing my current setup. However, while I like to tell myself it's about "opportunities to make my cloud box do things it never even could before", it's really more about the principle of the matter :P</p><p>So! Lessons learned when designing my hacky pile of Python + shell scripts to be more compatible with Nix next time:</p><ul><li>No need to shell out to <code>subprocess.run</code> when something in <code>os.shutil</code> can do the trick</li><li>If you do <code>subprocess.run</code> a shell script, invoke it directly so <code>patchShebangsAuto</code> can do its thing instead of an extra manual <code>substituteInPlace</code> step</li><li>Do NOT just dump state into the current directory, put it somewhere configurable please :)</li></ul><hr><p>Now!! I've finally finished porting all my services to NixOS, even testing them in a VM to make sure (thanks, <code>nixos build-vm</code>!). So why is the article title "Almost Ready To Switch" & not "Have Successfully Switched"? Well... my target is an Oracle Always Free box, so it is <code>aarch64-linux</code>, and my laptop is <code>x86_64-linux</code>, and <a href=https://github.com/NixOS/nixpkgs/issues/177873><code>nixos-rebuild</code> really doesn't like that situation</a><sup class=footnote-ref><a data-footnote-ref href=#fn-nixos-rebuild id=fnref-nixos-rebuild>3</a></sup>. There is a workaround where you use the <code>--fast</code> flag (to disable re-exec of Nix as <code>aarch64-linux</code> on your <code>x86_64-linux</code> host<sup class=footnote-ref><a data-footnote-ref href=#fn-reexec-bug id=fnref-reexec-bug>4</a></sup>) & a remote <code>--build-host</code>, unfortunately negating many of the effects of having a powerful builder locally. Still nice to have all your sources in one place ig.</p><p>It really is a rabbit hole with this Nix thing. "Ah yes just a little more effort and surely <em>then</em> i will have the perfect setup" is a trap that I am currently falling into, and one that I have seen many more experienced Nix users fall into too. Glad it's just a hobby project, at least I'm having fun :)</p><section class=footnotes data-footnotes><ol><li id=fn-getting-a-proper-setup><p>I once had a link to a pretty good tutorial I followed to get this all set up, but I have since lost it... all that remains is what's in my <a href=https://github.com/p0lyw0lf/infrastructure/tree/main/nixos>infrastructure</a> repo + the knowledge in my head that I should probably get into a blogpost some day. TL;DR nixos-infect + plain 'ol nixos-rebuild<sup class=footnote-ref><a data-footnote-ref href=#fn-nixos-rebuild id=fnref-nixos-rebuild-2>3</a></sup> have been a lot more reliable for me than things like nixos-anywhere or nixops. Then again there are bazillion similar blogs on this subject all with conflicting advice and I wasn't able to find the one I wanted by searching, so this would just be publishing into the void lol. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-getting-a-proper-setup>↩</a></p></li><li id=fn-on-standard-stuff><p>The other thing is, it was my first time packaging a poorly-written piece of code for Nix, so many of these problems were brand-new for me. Now that I am aware of these problems it is a lot easier to fix but yeah v frustrating the first time around :( <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-on-standard-stuff>↩</a></p></li><li id=fn-nixos-rebuild><p>Ok so actually maybe nixos-rebuild isn't all that great... <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-nixos-rebuild>↩</a> <a aria-label="Back to reference 3-2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3-2 href=#fnref-nixos-rebuild-2>↩<sup class=footnote-ref>2</sup></a></p></li><li id=fn-reexec-bug><p>Fun fact I think the checks for this are broken on the current nixos-unstable and it just goes ahead and tried to re-exec anyway, to disastrous effect. I put up a repository with reproduction steps <a href=https://github.com/p0lyw0lf/nixos-rebuild-bug-repro>here</a>, may try to report an issue later. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-reexec-bug>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Purchases I Absolutely Must Make This Bandcamp Friday (#7)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-08-01-purchases-i-absolutely-must-make-this-bandcamp-friday-7-/" />
      <id>https://wolfgirl.dev/blog/2025-08-01-purchases-i-absolutely-must-make-this-bandcamp-friday-7-/</id>
      <published>2025-08-01T13:07:17Z</published>
      <updated>2025-08-01T13:07:17Z</updated>
      <summary><![CDATA[Besides the ones I already linked in the rest of my [#music](https://wolfgirl.dev/tags/music/) tag posts, obviously. Going through follow...]]></summary>
      <content type="html"><![CDATA[<p>Besides the ones I already linked in the rest of my <a href=https://wolfgirl.dev/tags/music/>#music</a> tag posts, obviously. Going through followed artists actually uncovered a lot of great stuff I would have otherwise missed had I only stuck with regular listening habits, neat! As a reminder, Bandcamp Friday is a 24hr period where Bandcamp takes no cut, passing all funds it gets directly to artists, so it's the best time to buy all the stuff we've been saving up!</p><h2><a aria-hidden=true class=anchor href=#ninajirachi--girl-edm-disc-1 id=heading-ninajirachi--girl-edm-disc-1></a><a href=https://ninajirachi.bandcamp.com/album/girl-edm-disc-1>Ninajirachi – girl EDM (disc 1)</a></h2><p><img alt="Split-screen, a smeared, ghostly angelic figure with anime features materializes" src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/girl-edm-1.jpg></p><p>Very fun & energetic EDM. Album description on Bandcamp:</p><blockquote><p>there are girls and EDM and there is girl EDM... cat ears, furry paws, headphones on world off, sci-fi fantasy cosplay windows down hands in the air, kandi bracelet, computer, the next step in evolution and a window to the soul &lt;3</p></blockquote><p>i think that about sums it up ya!</p><p>Favorite track: 03 – Ninacamina <audio controls src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/03_Ninacamina.mp3></audio></p><p>I'd also like to purchase her next album, <a href=https://ninajirachi.bandcamp.com/album/i-love-my-computer>I Love My Computer</a>, but that is only a pre-order so i am unsure if the full discount applies. oh well doing it anyways.</p><h2><a aria-hidden=true class=anchor href=#bad-computer-feat-gold--connected id=heading-bad-computer-feat-gold--connected></a><a href=https://monstercatmedia.bandcamp.com/track/connected>Bad Computer feat. Gold – Connected</a></h2><p><img alt='Vaporwave-style 3D render with flowing gold statues, a cherry blossom branch, a checkerboard ceiling, and a Windows 95 "error connecting to internet" dialog, among other things' src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/connected.jpg></p><p>Not an album, just a single. Also getting <a href=https://ncsmusic.bandcamp.com/track/move-like-this>Move Like This</a>, basically everything Bad Computer puts out immediately goes on my favorites playlist.</p><p>Pretty much their own microgenre of EDM at this point, very distinctive style, just part of that classic Monstercat sound ya know? u could load a Rocket League menu to this</p><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/01_Connected.mp3></audio></p><h2><a aria-hidden=true class=anchor href=#emplexx--sunnexo--control-shift id=heading-emplexx--sunnexo--control-shift></a><a href=https://sunnexo.bandcamp.com/album/control-shift>Emplexx & Sunnexo – Control Shift</a></h2><p><img alt="Emi and Sunnexo strike a pose on their respective halves of the album cover. Emi is a normal girl on a blue background with various colored patches, and Sunnexo is a robot girl on a black computer-esque background. There's an overall pixelated aesthetic" src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/control-shift.jpg></p><p>It is Emplexx & Sunnexo, two indie artists who I already really enjoy separately, doing a collab!! Must-buy single for sure yaya</p><p>They call this genre "complextro 2" and who am i to argue with the masters?</p><p><audio controls src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/Control_Shift.mp3></audio></p><p>They also wrote about the making-of process on their blog, including the visuals! Very cool behind-the-scenes stuff, u should check it out: <a href=https://sunnexo.moe/controlshift>https://sunnexo.moe/controlshift</a></p><h2><a aria-hidden=true class=anchor href=#various-artists--emogen-3 id=heading-various-artists--emogen-3></a><a href=https://lostfrog.bandcamp.com/album/emo-gen-3>Various Artists – emo.gen 3</a></h2><p><img alt="Futuristic forlorn-looking anime girl clad in regal white dress against a dark background, with retro microphones suspended on thin strands from out of frame" src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/emo-gen-3.jpg></p><p>Doctór! inject that breakcore directly into my veins, please :3</p><p>Apparently this may be the final "emotional breakcore" compilation? As much as I enjoy having Literal Hours Of Breakcore to jam to & crave more, I can understand it is a massive effort to put together, plus there are two more like this lol.</p><p>technically u don't have to pay for this one but i did anyways just to be polite. haven't listened to the whole thing yet, just choosing the track by which i discovered it:</p><p>38 – Yem – Twin Hearts <audio controls src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/38_Twin_Hearts.mp3></audio></p><p>UPDATE 2025-08-10: I have listened to the whole thing now. 11, 12, 26, 27, 32, 33, 34, 35, & 36 r all Pretty Good but i still think 38 is the most interesting. got it in one huh !</p><h2><a aria-hidden=true class=anchor href=#jamie-paige--constant-companions-deluxe-edition id=heading-jamie-paige--constant-companions-deluxe-edition></a><a href=https://jamiepaige.bandcamp.com/album/constant-companions-deluxe-edition>Jamie Paige – Constant Companions (Deluxe Edition)</a></h2><p><img alt="A human hand holding a black cat paw hand, surrounded by fun doodles crawling over both" src=https://static.wolfgirl.dev/polywolf/blog/01986326-c3ea-7119-8ff0-ebe7b38e38d0/constant-companions-deluxe-edition.jpg></p><p>yes i know i posted about the normal version before. but this one has <a href="https://www.youtube.com/watch?v=0iVlSNpq8i8">BIRDBRAIN</a> on it. and <a href="https://www.youtube.com/watch?v=_AjJZEcMdww">ROT FOR CLOUT</a> again. i would buy another album for those two songs again :P</p><p>No need to post track snippets for this one, just watch those youtube videos if you haven't already. soon u too will be up birding ur brain & rotting ur clout :)))</p><hr><p>Anyways, there are many, <em>many</em> more tracks I'm buying (including some for a project I don't want to spoil!!), these are just the ones I thought were worth sharing today. Now go! Go forth and consume!!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I did my first V7 boulder today!??]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-07-22-i-did-my-first-v7-boulder-today-/" />
      <id>https://wolfgirl.dev/blog/2025-07-22-i-did-my-first-v7-boulder-today-/</id>
      <published>2025-07-22T14:35:11Z</published>
      <updated>2025-07-22T14:35:11Z</updated>
      <summary><![CDATA[did not think it would come so soon lol. been doing V5s somewhat consistently so i thought "ah, surely my third[^1] V6 will come soon" bu...]]></summary>
      <content type="html"><![CDATA[<p>did not think it would come so soon lol. been doing V5s somewhat consistently so i thought "ah, surely my third<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> V6 will come soon" but no, jumped a grade instead lol</p><p><img alt="Red V7 with a double start in the corner between two volumes. Both start holds are like platforms. The next hold is a narrow pincher with a wide platform (so, easy to grab) connected to a volume sticking far out from the wall on the right (so you have to jump to it), then two more slightly smaller of the same that are on the wall to the left of the volume. There's a pretty good crimp on the far side of the upper volume, and the top finishes with an upside-down hold directly above the hold you jump to." title="dyno time :3" src=https://static.wolfgirl.dev/polywolf/blog/01983264-d0fb-7332-85e9-2ed400f9cedb/IMG_0098.jpeg></p><p>It's a fun one!</p><details><summary>Here's my beta:</summary> <ol><li>Facing away from the wall, just put hands on both starting holds to lift yourself off the ground. Start complete!</li><li>Left foot on lower hold first, then work your right foot onto the upper hold, making sure to get your toe on it instead of your heel. Most people struggle w/ this (it is a bit of a flexibility check)</li><li>Jump to the hold :) Curl your body after making contact with the hold so your feet land just below the volume, stabilization is key.</li><li>Left hand goes up, bumps to next. Feet are still smearing to help with this.</li><li>Heel-hook the hold your right hand is on, and match your right hand to your left. Some ppl do toe but I find heel is a lot easier here.</li><li>Now that your hands are solid, switch your right foot to the toe, and reach with your right to the crimp.</li><li>Use that pull to get your weight way over your right leg, switching your left hand from a pinch to a post.</li><li>Use that post to help get your left leg onto the hold with your right leg.</li><li>As you're standing up, reach with your left for the finish hold, then match with your right.</li></ol></details><p>I do think this one counts as a real V7. It is a strength + flexibility test V7, but I can pass that test, so yeah let's go!!! feels good to <a href=https://wolfgirl.dev/blog/2025-01-05-new-year-s-resolutions/>keep a resolution</a> :)</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Technically, I've also done this yellow V6 that's in the gym currently, but I don't count it because most everyone agrees it is Too Easy, tho it's also very fun to do. I'd grade it a V4 tops. Similarly, there are other V5s in the gym that I've flashed that I'd personally grade like V4 or even V3. Maybe the setters are soft, or maybe I've been getting a lot better at certain styles? idk <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[From The Lowe's Parking Lot]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-07-18-from-the-lowe-s-parking-lot/" />
      <id>https://wolfgirl.dev/blog/2025-07-18-from-the-lowe-s-parking-lot/</id>
      <published>2025-07-18T20:57:25Z</published>
      <updated>2025-07-18T20:57:25Z</updated>
      <summary><![CDATA[i went to pick up a microwave and there were no microwaves that fit my cabinet and wirecutter said to get a different countertop microwav...]]></summary>
      <content type="html"><![CDATA[<p>i went to pick up a microwave and there were no microwaves that fit my cabinet and wirecutter said to get a different countertop microwave so instead i got random kitchen supplies</p><p><br><br> <br><br></p><p><img alt="fully clouded sky makes interesting patterns over green fuel tanks with the lights just turning on for the night" title="fully clouded sky makes interesting patterns over green fuel tanks with the lights just turning on for the night" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9962.JPG></p><p><br><br> <br><br></p><p><img alt="a flatbed in front of a forklift in front of a camper van in front of a garbage dump combed over by excavators in front of an elevated highway" title="a flatbed in front of a forklift in front of a camper van in front of a garbage dump combed over by excavators in front of an elevated highway" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9958.JPG></p><p><br><br> <br><br></p><p><img alt="rows of old empty metal shelving line the parking lot" title="rows of old empty metal shelving line the parking lot" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9963.JPG></p><p><br><br> <br><br></p><p><img alt="slightly dilapidated red brick building with faux industrial metal 77 painted on it nevertheless has real industry going on around with, with piles of various construction materials at its base and a train running overhead" title="slightly dilapidated red brick building with faux industrial metal 77 painted on it nevertheless has real industry going on around with, with piles of various construction materials at its base and a train running overhead" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9960.JPG></p><p><br><br> <br><br></p><p><img alt="taking a closer look at the train tracks. the sun makes them look beautiful" title="taking a closer look at the train tracks. the sun makes them look beautiful" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9951.JPG></p><p><br><br> <br><br></p><p><img alt='person walking outside a red brick building whose small chimney has been graffitied to say "ok"' title='person walking outside a red brick building whose small chimney has been graffitied to say "ok"' src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9972.JPG></p><p><br><br> <br><br></p><p><img alt="below the train tracks, a large set of steel girders hold up the blue-beige bridge they run on. it's almost the same color as the sky" title="below the train tracks, a large set of steel girders hold up the blue-beige bridge they run on. it's almost the same color as the sky" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9955.JPG></p><p><br><br></p><h2><a aria-hidden=true class=anchor href=#bonus-photo id=heading-bonus-photo></a>Bonus Photo</h2><p><a href=https://wolfgirl.dev/blog/2025-07-17-hardly-anyone-seems-to-know-or-care-about-editing-xdr-photos/>See yesterday's post</a> :3</p><p>also Greg said Adobe Lightroom for iOS should be able to produce jpegs with HDR gain maps and it even had the toggle switch for it but idk if it did that right or if Photos/Safari just isn't displaying them right... (click thru to see non-webp versions) ah well least i tried</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Hardly Anyone Seems To Know Or Care About Editing XDR Photos]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-07-17-hardly-anyone-seems-to-know-or-care-about-editing-xdr-photos/" />
      <id>https://wolfgirl.dev/blog/2025-07-17-hardly-anyone-seems-to-know-or-care-about-editing-xdr-photos/</id>
      <published>2025-07-18T01:33:26Z</published>
      <updated>2025-07-18T01:33:26Z</updated>
      <summary><![CDATA[In classic PolyWolf fashion, instead of finishing any of the N projects I have going on currently, I decided to take some photos yesterda...]]></summary>
      <content type="html"><![CDATA[<p>In classic PolyWolf fashion, instead of finishing any of the N projects I have going on currently, I decided to take some photos yesterday & post them to my blog. Quick, easy, in-n-out, 15min editing tops, just gotta redact some license plates, be a good citizen 'n all, how hard could it be? :)</p><p><img alt='orange diamond sign has been modified to say "Dork zone ahead", photo taken at a dutch angle of an urban car-lined street' title="it's me, thats my zone" src=https://static.wolfgirl.dev/polywolf/blog/01981ad4-f782-7991-9b01-4e6dfc3f4bd9/IMG_9976.jpeg></p><p>uh. so. some explanations are in order. <strong>XDR</strong> a.k.a. UltraHDR if u live in Google land instead of Apple land, is a feature by which displays will adjust their brightness beyond the system whitepoint when displaying compatible photos/videos. this can lead to some pretty striking effects; check out <a href=https://gregbenzphotography.com/hdr/>https://gregbenzphotography.com/hdr/</a> on your (somewhat recent) phone to see what I mean.</p><p>Honestly, I wish I had found Greg's website <em>much</em> sooner; it'd a godsend for exactly the information I was looking for (tysm greg ilyyyy &lt;3). Before finding his website, all i came across was this <a href=https://apple.stackexchange.com/questions/456892/how-to-produce-edit-and-view-hdr-photographs-format-on-xdr-displays-properly>lonely StackExchange post</a> w/ no responses & this <a href=https://discuss.pixls.us/t/manual-creation-of-ultrahdr-images/45004>random forum post</a> where they discuss manually creating gain maps to apply with command-line tools, which is a bit much even for me.</p><p>Before <em>that</em>, I tried out every image editing app I had available to see which one could preserve my image's XDR-ness when edited:</p><ul><li>iOS's default editor<sup class=footnote-ref><a data-footnote-ref href=#fn-default-editor id=fnref-default-editor>1</a></sup></li><li>MacOS's default editor</li><li>GIMP</li><li>Krita</li><li>Clip Studio Paint</li><li>Procreate</li><li>Affinity Photo</li><li>Adobe Fresco</li><li>hell, even Adobe Lightroom for iOS since that's free somehow</li></ul><p>Unfortunately, according to Greg, the only ones that could have possibly worked are GIMP, Affinity Photo, & Adobe Lightroom. And of those, only Adobe Lightroom has support for gain maps<sup class=footnote-ref><a data-footnote-ref href=#fn-gain-maps id=fnref-gain-maps>2</a></sup>, which my photo used. And! Out of all the apps I tried, Lightroom was the only one without a paintbrush tool :(((( no license plate redaction for me i guess...</p><p>So! What's done is done, and I have resigned myself to the fate of a normal Display P3 gamut image for this one, so sad i know. And listen, I already knew that my blog's image preprocessing likely would've wiped out the XDR-ness anyways, so all this effort wouldn't have affected the viewing experience at all, but it's about the principle of the thing y'know? a girl's gotta have principles</p><p>anyways hope i can upload the rest of those photos tomorrow. peace y'all ✌️</p><section class=footnotes data-footnotes><ol><li id=fn-default-editor><p>Yeah I have no idea how this one failed either. I guess they just don't expect people who markup their images to care that it's not super bright anymore, or even notice since the images ur marking up usually aren't XDR anyways?? very strange. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-default-editor>↩</a></p></li><li id=fn-gain-maps><p>There are two main ways to make your images super bright: gain maps and 32-bit. gain maps are when you store an extra image in the metadata alongside your images saying how much brighter each overall pixel should be, and 32-bit is when you just use a bunch of extra data to encode exactly how much bright each individual RGB channel should be across an enhanced brightness range. phone photos will often use gain maps because they're smaller in size and give visually comparable outputs, while things like camera raws will be in 32-bit RGB for maximum losslessness when processing in ur editing software. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-gain-maps>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: [FICTION] Bridge to eQualia]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-07-11-rechost-fiction-bridge-to-equalia/" />
      <id>https://wolfgirl.dev/blog/2025-07-11-rechost-fiction-bridge-to-equalia/</id>
      <published>2025-07-11T13:31:07Z</published>
      <updated>2025-07-11T13:31:07Z</updated>
      <summary><![CDATA[https://suricrasia.online/blog/bridge-to-equalia/  > Maggie, a security researcher moonlighting as a internet sleuth, uses her unique s...]]></summary>
      <content type="html"><![CDATA[<p>https://suricrasia.online/blog/bridge-to-equalia/</p><blockquote><p>Maggie, a security researcher moonlighting as a internet sleuth, uses her unique set of skills to investigate a case of online vandalism.</p><p>Content warnings: Unreality, vomit, drugs, alcohol, death, missing people, ableism, report cards, video game gore, claustrophobia.</p><p>You can play a visual novel adaptation of this story on <a href=https://blackle.itch.io/bteq>itch.io</a>.</p></blockquote><p>little bit late to the party on this one whoops :P good fiction is timeless, anything by Blackle Mori is an absolute banger, "absolute banger" > "good", ∴ i can review this whenever i want Q.E.D.</p><p>it's a noir-style short story, "homage" is maybe the wrong word, so "snapshot" perhaps, of a certain contemporary internet culture that i myself sometimes rub up against as a stereotypical Internet Transgender. I'd be interested to know how someone not as versed in the culture interprets it; would they develop a better understanding? would they miss a whole bunch of references that are essential to understanding? are the parts that i think are essential not actually? only one way to find out</p><p>masterfully executed prose & structure. i really appreciate the attention to detail on all the computery bits too, really soothes my soul. <a href=https://blackle.itch.io/bteq>itch.io</a> VN background pictures & text timing add a whole 'nother layer on ambiance to the experience that i really enjoyed. ty Blake Mori for the excellent story &lt;3</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Some Albums That Put Me In A Flow State (#6)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-07-01-some-albums-that-put-me-in-a-flow-state-6-/" />
      <id>https://wolfgirl.dev/blog/2025-07-01-some-albums-that-put-me-in-a-flow-state-6-/</id>
      <published>2025-07-01T23:22:42Z</published>
      <updated>2025-07-01T23:22:42Z</updated>
      <summary><![CDATA[so. "flow state"? when I'm working on something, sometimes i need some extra energy to keep me going. something with a strong beat, not _...]]></summary>
      <content type="html"><![CDATA[<p>so. "flow state"? when I'm working on something, sometimes i need some extra energy to keep me going. something with a strong beat, not <em>too</em> repetitive, simple lyrics if there are any, & preferably in the electronic genre. fortunately, there's a lot that fits this bill, and I keep finding it! i don't <em>have</em> to publish these posts monthly, but hey if there's enough good music out there, i might as well right? reverse ranked order because i'm feeling spicy >:)</p><h2><a aria-hidden=true class=anchor href=#4-auvic--voices-call id=heading-4-auvic--voices-call></a>4: <a href=https://auvicmusic.bandcamp.com/album/voices-call-remastered>Auvic – Voices Call</a></h2><p><img alt="Auvic – Voices Call album cover. abstract watercolor rainbow leaf shapes on a white background" src=https://static.wolfgirl.dev/polywolf/blog/0197b826-9f36-755a-bc29-f43e1627f9f9/voices_call.webp></p><p>That link is actually to the bandcamp remastered version, which doesn't contain all the songs seemingly? The original version on <a href=https://music.apple.com/us/library/albums/l.grUydjq>Apple Music</a> version has many more (and being honest I am not super into the remastering anyways but i digress). That original version is very 2015 in its soundfont, makes me feel groovy, bouncing in my seat a little, saying "woo, yeah", etc.</p><p>I do not know genres very well but I will try my best: upbeat electro, inspired by chiptune in some ways, but mostly it's its own thing. Once again, very 2015.</p><p>Favorite track: 08 – Upgrades <audio controls src=https://static.wolfgirl.dev/polywolf/blog/0197b826-9f36-755a-bc29-f43e1627f9f9/Upgrades.mp3></audio><sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup></p><h2><a aria-hidden=true class=anchor href=#3-virtual-riot--simulation-deluxe-version id=heading-3-virtual-riot--simulation-deluxe-version></a>3: Virtual Riot – Simulation (Deluxe Version)<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup></h2><p><img alt="Virtual Riot – Simulation (Deluxe Version) album cover. man in a VR headset falls to his knees in a dingy alleyway" src=https://static.wolfgirl.dev/polywolf/blog/0197b826-9f36-755a-bc29-f43e1627f9f9/simluation_deluxe_edition.jpg></p><p>No bandcamp link, just an <a href=https://music.apple.com/us/library/albums/l.UMB2rW3>Apple Music</a> one sorry. The original is probably a more cohesive album, the only reason I include this one is because (a) more songs = more flow & (b) between Deluxe/non-Deluxe, it has my favorite song, so yeah :)</p><p>Virtual Riot has a long track record of releasing very good color bass (i think that's the right genre label?), <a href=https://soundcloud.com/dylantallchief/dylan-tallchief-crow-always-let-you-stop-virtual-riot-tribute>never let me down</a> really, but that's all mostly in singles, so it's nice to get a full-length album from them to just plow through when I need that good good.</p><p>Favorite track: 17 – Bossfight Afterparty <audio controls src=https://static.wolfgirl.dev/polywolf/blog/0197bb3b-6df8-7ddb-b58d-b25685756593/Bossfight_Afterparty.mp3></audio></p><h2><a aria-hidden=true class=anchor href=#2-adam-tell--peripheries id=heading-2-adam-tell--peripheries></a>2: <a href=https://adamtellmusic.bandcamp.com/album/peripheries>Adam Tell – Peripheries</a></h2><p><img alt="Adam Tell – Peripheries album cover. watercolor of starry sky thru an opening in the trees" src=https://static.wolfgirl.dev/polywolf/blog/0197b826-9f36-755a-bc29-f43e1627f9f9/peripheries.jpg></p><p>I mostly knew Adam Tell from his lyrical work/singing in <a href=https://music.apple.com/us/song/superseded/1171244991>Superseded</a>, <a href=https://adamtellmusic.bandcamp.com/album/falsework>Falsework</a>, & <a href=https://jkuch.bandcamp.com/track/jkuch-adam-tell-data-loss>Data Loss</a>. Turns out he can work those hardware synths too!! Bit darker than the other ones so far, but it's kind of like that starry night nighttime darkness, kinda cozy in a way at times.</p><p>Feels like it'd fit right at home in an <a href=https://www.youtube.com/@ExplosionsAndFire/videos>Explosions&Fire</a> video during one of those chemistry montages , so I guess that puts it in the ambient genre? anyways</p><p>Favorite track: 01 – Transfer Orbit <audio controls src=https://static.wolfgirl.dev/polywolf/blog/0197bb3b-6df8-7ddb-b58d-b25685756593/Transfer_Orbit.mp3></audio></p><h2><a aria-hidden=true class=anchor href=#1-free99--out-for-blood id=heading-1-free99--out-for-blood></a>1: <a href=https://xfree99x.bandcamp.com/album/out-for-blood>free.99 – Out For Blood</a></h2><p><img alt="free.99 – Out For Blood. a bloody, distorted face with the album name overlaid" src=https://static.wolfgirl.dev/polywolf/blog/0197b826-9f36-755a-bc29-f43e1627f9f9/out_for_blood.jpg></p><p>Trans Pride is over. now is the time for Trans Wrath /j</p><p>in all seriousness, this album is very angry. overall i am better off just choosing not to comprehend the lyrics<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup>, treating the words as if they were from a foreign language instead, getting all i need to know from keywords/tone of voice. which is to say: get angry!</p><p>never fails to light a fire under me. however, because the concepts slide right off me, it is more "generic high energy" than "angry" for me at this point. still feels like a problematic fave, but whatever it's <em>my</em> problematic fave, and you can't take it away from me :P</p><p>genre feels like highly produced rave music, if i had to describe it. very move-to-able.</p><p>Favorite track: 02 – Lilith <audio controls src=https://static.wolfgirl.dev/polywolf/blog/0197b826-9f36-755a-bc29-f43e1627f9f9/Lilith.mp3></audio></p><hr><p>That's it for this month, hope you found something new you liked! Music is very important to me and I hope you enjoy it too &lt;3</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I got the idea for this from Plum's <a href=https://www.plumpan.net/main-blog/plums-music-everyone-should-listen-to>massive music list everyone should listen to</a>, which has 30-second clips for every single one of the albums he recommends. I generate these clips with <code>ffmpeg -ss '00:mm:ss' -i 'input file.mp3' -t '00:00:30' 'output file.mp3'</code>, given the timestamp, input file, and output file I want to generate, and given that I already have the music downloaded ofc. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>Also, is it "Deluxe Version", or "Deluxe Edition"? The title and album cover can't seem to agree :P <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>Being a bit dramatic here, it's not like the <em>most</em> problematic stuff that's ever been sung, pretty tame by many standards, but it's getting up there. like i said, angry. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: everything else!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-06-08-rechost-everything-else-/" />
      <id>https://wolfgirl.dev/blog/2025-06-08-rechost-everything-else-/</id>
      <published>2025-06-08T23:36:57Z</published>
      <updated>2025-06-08T23:36:57Z</updated>
      <summary><![CDATA[https://meow.garden/everything-else/  > You can watch the interactive music video on your phone [here](https://www.youtube.com/watch?v=...]]></summary>
      <content type="html"><![CDATA[<p>https://meow.garden/everything-else/</p><blockquote><p>You can watch the interactive music video on your phone <a href="https://www.youtube.com/watch?v=4XF5ta-ULjk">here</a> — <strong>turn on rotation lock</strong> & don't drop your device! If you want to watch it on desktop or TV, or if you don't want to have to rotate your device, watch the <a href="https://www.youtube.com/watch?v=qITGvxw5Vhk">static version</a> instead.</p></blockquote><p>this music video/multimedia artwork project had a bit of an impact on me (forced a few tears out). very very good song and a very very good retrospective post on the personal journey of making the song.</p><p>The post cites "Melodic, emotional drum & bass", as the target, and Ash really knocked it out of the park on this one. Later on they mention Porter Robinson & Virtual Riot as inspirations when it came to the vocal processing, and I can definitely hear that reflected in the beats too. I think "EVERYTHING!" even holds up against those titans? in my ears, at least. Like I said, very very good stuff :3</p><p>hard to exactly put into words the emotions i got out of it but i will try anyways: it's about youth, it's about queer joy, it's about that time of night when you're hanging out with your friends & you completely forget yourself & all your worries, or that time where re-unite with friends after a couple of years & everything just clicks back into place, it's about in-jokes & cat pictures & late-night discord calls. it's about Ash, but it's also about you & me. that's all i got, peace</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I'm A Nixpkgs Maintainer Now]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-06-07-i-m-a-nixpkgs-maintainer-now/" />
      <id>https://wolfgirl.dev/blog/2025-06-07-i-m-a-nixpkgs-maintainer-now/</id>
      <published>2025-06-07T23:09:09Z</published>
      <updated>2025-06-07T23:09:09Z</updated>
      <summary><![CDATA[ok so here's the story: before i was writing ["Packaging Multiple Dependent Python Modules Using Hatch & Nix"](https://wolfgirl.dev/blog/...]]></summary>
      <content type="html"><![CDATA[<p>ok so here's the story: before i was writing <a href=https://wolfgirl.dev/blog/2025-05-15-packaging-multiple-dependent-python-modules-using-hatch-nix/>"Packaging Multiple Dependent Python Modules Using Hatch & Nix"</a>, i was trying to package my website's <a href=https://github.com/p0lyw0lf/crossposter>"backend"</a>. unfortunately, i hit a stumbling block more stumble-y than i ever could have anticipated: one of the key Python dependencies I relied on (the <a href=https://sanic.dev/en/>Sanic</a> webserver) was unmaintained in nixpkgs, crashing w/ a <code>ModuleNotFoundError</code> 🙀</p><p>fortunately, i know nix{pkgs} now, so i was capable of <a href=https://github.com/NixOS/nixpkgs/pull/409605>resolving the problem</a>. tho i might as well <a href=https://github.com/NixOS/nixpkgs/pull/409599>update Sanic while i was at it</a>. and <a href=https://github.com/NixOS/nixpkgs/pull/410499>add myself as maintainer for the things I touched</a>. and <a href=https://github.com/NixOS/nixpkgs/pull/410691>add that sanic-ext package i need for my backend too</a>. oh wait yeah that third one right.</p><h2><a aria-hidden=true class=anchor href=#what-does-being-a-maintainer-mean id=heading-what-does-being-a-maintainer-mean></a>What Does Being A Maintainer Mean??</h2><p>All it means is, I will automatically get pinged if someone opens a PR to make changes to these packages in the future :) Doesn't mean I have commit privileges yet! Tho, staring down the never-ending deluge of PRs that crash against nixpkgs daily, I'm not sure I want to really? It's a lot of power, but also a lot of responsibility (<img alt=spiderman src=https://static.wolfgirl.dev/polywolf/blog/01974c83-e2a2-7bb3-acae-aeb9b4cea33e/Mvc2-spiderman.png style=vertical-align:bottom;width:32px;height:27.5px;display:inline title=spiderman>). Participating in the process for what happens if your PR drops off the first couple pages (you apologetically post in a Matrix room and a committer very graciously merges it) has been humbling, really reinforces that this is a community effort to make this thing run as smoothly as it does. Now that I've taken a closer look at the gears, I am more awe-struck than ever; there is <em>such</em> a massive amount of labor put into this project, for free, in people spare time, because they believe in it, just like I did. Something special, I think :)</p><p>As for why I waited until my last PR was merged (which took about 2 weeks), instead of when my maintainership status was official (2 weeks ago, just 3 minutes from PR to merge, surely some sort of record) to make this post? Well, ya know, girls be silly sometimes :P</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[MTG Draft: A New Beginner's Experience]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-06-01-mtg-draft-a-new-beginner-s-experience/" />
      <id>https://wolfgirl.dev/blog/2025-06-01-mtg-draft-a-new-beginner-s-experience/</id>
      <published>2025-06-02T00:41:37Z</published>
      <updated>2025-06-02T00:41:37Z</updated>
      <summary><![CDATA[I played MTG Draft w/ the Bloomburrow set last night. If you, like me 48hr ago, know about Magic: The Gathering as a card game but don't...]]></summary>
      <content type="html"><![CDATA[<p>I played MTG Draft w/ the Bloomburrow set last night. If you, like me 48hr ago, know about Magic: The Gathering as a card game but don't really know what "Draft" is, here's my high-level explanation: you craft your deck from a limited pool of cards along with a bunch of other players. Here's the actual draft algorithm:</p><ul><li>Each player gets 3 booster packs, 14 cards each</li><li>Repeat following steps for each booster pack: <ul><li>Open the booster pack</li><li>Pick 1 card from the pack</li><li>Pass the remaining cards to the next player</li><li>Repeat the above two steps until there are no more cards in the pack to be picked</li></ul></li></ul><p>Because each booster pack is random, and any broadly good cards will likely be taken early, this format naturally leads to more "casual"-style and -power decks, which is definitely more my speed. Also, this set more than others is very tribe-heavy, u just gotta pick ur cute animal & roll w/ it, very beginner friendly!!</p><p><img alt="Bloomburrow booster back with artwork of a mouse in a field" src=https://static.wolfgirl.dev/polywolf/blog/019725fe-e7b7-7884-9f18-c1cda083d91f/IMG_9656.png></p><p>unfortunately i did not get the cutest animal (that would be rabbits), ended up doing something midrange-y with squirrels and the black half of a bats package. Overall, I didn't find the drafting part too hard, or the "identifying the best cards" for the deck construction part, it was the act of actually <em>playing</em> Magic: The Gathering that set my brain on fire & made me feel like I had no earthly idea what I was doing.</p><h2><a aria-hidden=true class=anchor href=#game-1 id=heading-game-1></a>Game 1</h2><p>For <em>both rounds</em>, my opponent gets two 2/3 fliers on board by turn 4, and is able to use targeted removal on any fliers I put up to block (not that I get any big enough fliers those rounds), and none of my guys have reach, and also I don't get any of my own removal, so ggs no re onto the next. Despite this very simple description, it still almost went to time because I take a long time to make all decisions because I am new at the game (all other games went to time)</p><p><img alt="Moonrise Cleric {1}{W/B}{W/B} Creature – Bat Cleric 2/3: Flying. Whenever Moonrise Cleric attacks, gain 1 life." src=https://static.wolfgirl.dev/polywolf/blog/019725fe-e7b7-7884-9f18-c1cda083d91f/IMG_9657.jpeg></p><p>I actually had this guy in my deck and so learned so play him in future games.</p><h2><a aria-hidden=true class=anchor href=#game-2 id=heading-game-2></a>Game 2</h2><p>Stomped in the first round due to the above card and also this guy (aggro flyers my beloved):</p><p><img alt="Starscape Cleric {1}{B} Creature – Bat Cleric 2/1: Offspring {2}{B}. Flying. This creature can't block. Whenever you gain life, each opponent loses 1 life." src=https://static.wolfgirl.dev/polywolf/blog/019725fe-e7b7-7884-9f18-c1cda083d91f/IMG_9660.jpeg></p><p>But then got stomped in the second due to a "board wipe" (it was a Blue deck so just a "return to hand" effect but basically same thing for me).</p><p>Last game went to time, and I was doing pretty badly again, hanging on with just 2 life & an empty board against 19 life & a full board when the turn limit hit. I thought I would have lost at that point but turns out it's a dice roll-off? which i won?? which seems dumb but i'll take the undeserved W ig</p><h2><a aria-hidden=true class=anchor href=#game-3 id=heading-game-3></a>Game 3</h2><p>Got stomped in the first round due to an extremely wide rabbits board. Rabbits are the cutest clan but also extremely scary thanks to the following card:</p><p><img alt="Burrowguard Mentor {G}{W} Creature – Rabbit Soldier /: Trample. Burrowguard Mentor's power and toughness are equal to the number of creatures you control." src=https://static.wolfgirl.dev/polywolf/blog/019725fe-e7b7-7884-9f18-c1cda083d91f/IMG_9662.jpeg></p><p>so yeah once I was facing down a 6/6 version of that (due to creature token creation elsewhere) there wasn't any coming back :P ggs onto next.</p><p>My curve the next game was out of this world, got to play this guy with full stats on turn 4:</p><p><img alt="Bushy Bodyguard {1}{G} Creature – Squirrel Warrior 2/1: Offspring {2}. When this creature enters, you may forage. If you do, put two +1/+1 counters on it" src=https://static.wolfgirl.dev/polywolf/blog/019725fe-e7b7-7884-9f18-c1cda083d91f/IMG_9658.jpeg></p><p><strong>and</strong> activate this guy's ability:</p><p><img alt="Bonecache Overseer {B} Creature – Squirrel Warlock 1/1: Tap, Pay 1 life: Draw a card. Activate only if three or more cards have left your graveyard this turn or you've sacrificed a Food this turn." src=https://static.wolfgirl.dev/polywolf/blog/019725fe-e7b7-7884-9f18-c1cda083d91f/IMG_9659.jpeg></p><p>So yeah. My board was pretty juicy otherwise too, able to stomp out opponent's board development before it could happen. ggs onto round 3</p><p>This final round was a lot closer. At this point in the night, despite having fully burnt out my decision-making circuitry, I was getting the hang of building boards & managing resources and we were tied at around 14 life apiece. However, it being 10 minutes past closing time and I being the only one eligible for prizing, I generously gifted one of my "yay you went 2-1" booster packs in exchange for officially being recorded as going 2-1 (the other person had a 0-2 record going in and we wouldn't have gotten prizing otherwise).</p><h2><a aria-hidden=true class=anchor href=#concluding-thoughts id=heading-concluding-thoughts></a>Concluding Thoughts</h2><p>Despite being very overwhelmed for the entire process, I ended up having a pretty fun time and came out with a much better grasp on the game. "Trial by fire" 'n all. The friends I went with (friend in town from out-of-state who to experience a different scene, and a local friend who hasn't drafted in maybe 7 years), were not in my pod, and are much more experienced MTG players than I am, and ended up doing very well (3-0 and 2-1 respectively, with the only loss being from when they were paired up against each other). All in all a great time all around, probably not doing it again any time soon tho :P</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Some More Albums I Like Listening To (#5)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-05-27-some-more-albums-i-like-listening-to-5-/" />
      <id>https://wolfgirl.dev/blog/2025-05-27-some-more-albums-i-like-listening-to-5-/</id>
      <published>2025-05-28T01:20:25Z</published>
      <updated>2025-05-28T01:20:25Z</updated>
      <summary><![CDATA[As good a time to start numbering these as any :P You can find previous collections in my [#music](https://wolfgirl.dev/tags/music/) tag....]]></summary>
      <content type="html"><![CDATA[<p>As good a time to start numbering these as any :P You can find previous collections in my <a href=https://wolfgirl.dev/tags/music/>#music</a> tag. Without further ado, let's get into it!</p><hr><h2><a aria-hidden=true class=anchor href=#1-amamelia---bananamelia id=heading-1-amamelia---bananamelia></a>1: <a href=https://amamelia.bandcamp.com/album/bananamelia>Amamelia - Bananamelia!</a></h2><p><img alt="single dew-wet leaf in front of a boxy rainbow & sky gradient" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9647.jpeg></p><p><a href=https://social.treehouse.systems/@rykarn/114431434221521450>rykarn recommended this one</a>!! from the artist page:</p><blockquote><p>Auckland based trans weirdo electronic breaks and house</p></blockquote><p>so yeah, that's what it's all about, no wonder i enjoyed it :3</p><p>Compared to many other contemporary songs, the hooks on these aren't quite as strong, but in exchange, they hit a lot harder once you match yourself to their pace. Definitely worth listening to as a full album for that reason.</p><p><em>Favorite track: 4. Colorbox</em></p><h2><a aria-hidden=true class=anchor href=#2-ok-go---hungry-ghosts id=heading-2-ok-go---hungry-ghosts></a>2: <a href=https://paracadute.bandcamp.com/album/hungry-ghosts>OK Go - Hungry Ghosts</a></h2><p><img alt="The 4 members of OK Go in a square grid, where each member & their green + gray background has been digitally altered to be sliced + spliced a bit, but the overlaid OK Go letters have not" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9648.jpeg></p><p>You all know them from their fantastic music videos, and I'll admit I was already familiar with many a track on this album thanks to those too. But! I just really like all the tracks on here?? Even the ones I haven't seen videos for before. These guys rock.</p><p><em>Favorite track: 1. <a href=https://youtu.be/LWGJA9i18Co>Upside Down & Inside Out</a></em></p><h2><a aria-hidden=true class=anchor href=#3-pedestrian-tactics---nerds-trying-to-dance-ep id=heading-3-pedestrian-tactics---nerds-trying-to-dance-ep></a>3: <a href=https://madzoo.bandcamp.com/album/nerds-trying-to-dance-ep>Pedestrian Tactics - Nerds Trying To Dance EP</a></h2><p><img alt="close-up of a computer monitor such that you can see the individual LCD cells, showing an old browser navigating to nttd://" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9649.jpeg></p><p>A rare W for Apple Music algorithmic suggestions. Usually it feeds me "niche stuff I've already listened to" or "pop music that I don't particularly like", but once in a blue moon it'll hit me with the new stuff that I vibe with. Best part is, Pedestrian Tactics has an extensive catalog for me to go through, though so far this is my favorite EP of theirs. Slightly experimental electronic, extremely danceable.</p><p><em>Favorite track: 2. Nerds Trying To Dance</em></p><h2><a aria-hidden=true class=anchor href=#4-jamie-paige---constant-companions id=heading-4-jamie-paige---constant-companions></a>4: <a href=https://jamiepaige.bandcamp.com/album/constant-companions>Jamie Paige - Constant Companions</a></h2><p><img alt="An illustration of Jaime lays smiling on a picnic blanket next to an outline of a humanoid cat, basking in the sun" src=https://static.wolfgirl.dev/polywolf/blog/019713f0-c9b3-7775-83a5-4752d66f6b08/IMG_9650.jpeg></p><p>I think I bought this album as a pre-order when I was listening to a lot of other Jaime Paige and then just... forgot about it?? Either that or fell to the bottom of my pile while waiting for the "perfect moment" to listen to it, after experiencing the sublime artistry of the <a href=https://youtu.be/sqK-jh4TDXo>Machine Love</a> music video/album preview.</p><p>turns out the perfect moment is right after watching the <a href=https://youtu.be/h7an0I2yArw>berd rots</a> youtube video and getting ROT FOR CLOUT stuck in my head. who else up ROTTING THEY CLOUT /j</p><p>Bit more of a "collection of songs" than an "album listening experience" than the other albums I mentioned, but still very good. It's Jaime Paige after all, so every song is lyrical & fun to listen to.</p><p><em>Favorite Track: 3. ROT FOR CLOUT</em></p><hr><p>Oh yeah and update on <a href=https://wolfgirl.dev/blog/2025-04-30-some-albums-i-am-looking-forward-to/>my last post</a>: We Live In A Society was indeed p good, Track 1 is the most banger but The Ocean Is Fake is nice too, and unfortunately TOHO BOSSA NOVA 14 has not released on streaming services yet :( oh well</p><p>Also, as always, if you have any recommendations for things you think I should listen to, please let me know in the comments!! my ears r always hungry 4 more good tunes :3</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Friendship (2024 film)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-05-22-friendship-2024-film-/" />
      <id>https://wolfgirl.dev/blog/2025-05-22-friendship-2024-film-/</id>
      <published>2025-05-23T01:42:56Z</published>
      <updated>2025-05-23T01:42:56Z</updated>
      <summary><![CDATA[Great movie. I do not have any previous exposure to Tim Robinson or Paul Rudd comedy so this all felt very fresh, but even so other ppl a...]]></summary>
      <content type="html"><![CDATA[<p>Great movie. I do not have any previous exposure to Tim Robinson or Paul Rudd comedy so this all felt very fresh, but even so other ppl agree it's well-paced, immaculately shot, and just plain has something interesting to say despite being a comedy.</p><p>Main character is a dipshit & very cringe, but never unwatchably so, at least for me. He's a narcissist beyond all redemption, a child's impulse control in a man's body, a toxic human being rewarded by the toxic society he inhabits, and many other poignant descriptions <em>explicitly</em> pointed to by the film. And that's just one of the characters. Anyways I think it's a pretty good flick you should watch it.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Packaging Multiple Dependent Python Modules Using Hatch & Nix]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-05-15-packaging-multiple-dependent-python-modules-using-hatch-nix/" />
      <id>https://wolfgirl.dev/blog/2025-05-15-packaging-multiple-dependent-python-modules-using-hatch-nix/</id>
      <published>2025-05-15T11:05:37Z</published>
      <updated>2025-05-15T11:05:37Z</updated>
      <summary><![CDATA[Alternative title: "Overcomplicating My Website Backend Using Hatch & Nix". For the life for me, I could not figure out how to string all...]]></summary>
      <content type="html"><![CDATA[<p>Alternative title: "Overcomplicating My Website Backend Using Hatch & Nix". For the life for me, I could not figure out how to string all of this together until after pouring over the <a href=https://ryantm.github.io/nixpkgs/languages-frameworks/python/>nixpkgs manual</a> & <a href=https://nixos.wiki/wiki/Packaging/Python>NixOS Wiki</a> about 20 times each; no other resource applied cleanly to my situation. Here's hoping these instructions will help someone else going thru the same thing I did.</p><h2><a aria-hidden=true class=anchor href=#basic-setup id=heading-basic-setup></a>Basic Setup</h2><p>Say we have two Python applications that depend on a third, shared Python library. That is, something like the following:</p><pre class=syntax-highlighting><code class=language-python><span class=comment># shared/__init__.py</span>
<span class=constant>NAME</span> <span class=operator>=</span> <span class=string>"PolyWolf"</span></code></pre><pre class=syntax-highlighting><code class=language-python><span class=comment># hello/__main__.py</span>
<span class=keyword>from</span> <span class=variable>shared</span> <span class=keyword>import</span> <span class=constant>NAME</span>
<span class=keyword>if</span> <span class=variable>__name__</span> <span class=operator>==</span> <span class=string>"__main__"</span>:
    <span class=function>print</span>(<span class=string>f"Hello, </span><span class=punctuation>{</span><span class=constant>NAME</span><span class=punctuation>}</span><span class=string>"</span>)</code></pre><pre class=syntax-highlighting><code class=language-python><span class=comment># goodbye/__main__.py</span>
<span class=keyword>from</span> <span class=variable>shared</span> <span class=keyword>import</span> <span class=constant>NAME</span>
<span class=keyword>if</span> <span class=variable>__name__</span> <span class=operator>==</span> <span class=string>"__main__"</span>:
    <span class=function>print</span>(<span class=string>f"Goodbye, </span><span class=punctuation>{</span><span class=constant>NAME</span><span class=punctuation>}</span><span class=string>"</span>)</code></pre><p>Then, we can run them like so:</p><pre class=syntax-highlighting><code>$ nix-shell -p python3 tree
fetching path input 'path:/nix/store/1dwky0bis4bkl3qngsc6pmq902swa9b6-source'

[nix-shell:/tmp/blogpost]$ tree
.
├── goodbye
│   └── __main__.py
├── hello
│   └── __main__.py
└── shared
    └── __init__.py

4 directories, 3 files

[nix-shell:/tmp/blogpost]$ python3 -m hello
Hello, PolyWolf

[nix-shell:/tmp/blogpost]$ python3 -m goodbye
Goodbye, PolyWolf
</code></pre><p>What the programs actually do doesn't matter all too much; what matters is the module structure, and the directory structure. We want <code>shared</code> to be, well, <em>shared</em> between the <code>hello</code> & <code>goodbye</code> programs, and for no one program to be the obvious one to contain all the shared code. This represents how my <a href=https://github.com/p0lyw0lf/crossposter>crossposter</a> is structured, and is representative of many other "monorepo"-style applications too<sup class=footnote-ref><a data-footnote-ref href=#fn-python-vs-js id=fnref-python-vs-js>1</a></sup>.</p><h3><a aria-hidden=true class=anchor href=#pythonpath-and-you id=heading-pythonpath-and-you></a><code>PYTHONPATH</code> And You</h3><p>As a slight aside, there's a specific reason we ran the programs with the <code>-m</code> flag earlier. To see this, let's try running without it:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ python3 hello
Traceback (most recent call last):
  File "&lt;frozen runpy>", line 198, in _run_module_as_main
  File "&lt;frozen runpy>", line 88, in _run_code
  File "/tmp/blogpost/hello/__main__.py", line 1, in &lt;module>
    from shared import NAME
ModuleNotFoundError: No module named 'shared'
</code></pre><p>Oh no the dreaded <code>ModuleNotFoundError</code>!! Reading around a bit, we learn the module search path is stored in the <code>sys.path</code> variable, and learn about <a href=https://docs.python.org/3/library/sys_path_init.html>its initialization procedure</a>:</p><blockquote><p>The first entry in the module search path is the directory that contains the input script, if there is one. Otherwise, the first entry is the current directory, which is the case when executing the interactive shell, a -c command, or -m module.</p></blockquote><p>Aha! So that's why: In the <code>-m</code> example, the <code>/tmp/blogpost</code> directory was added to the module path, because there was no "input script", just a module to be run. But without <code>-m</code>, the <code>/tmp/blogpost/hello</code> directory was added, because that's where <code>__main__.py</code> lives, and that directory does <em>not</em> contain a <code>shared</code> module. Is there a workaround? Fortunately, yes, in the next paragraph:</p><blockquote><p>The <code>PYTHONPATH</code> environment variable is often used to add directories to the search path. If this environment variable is found then the contents are added to the module search path.</p></blockquote><p>So! If we really wanted, we could do something like:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ PYTHONPATH="$(pwd):$PYTHONPATH" python3 hello
Hello, PolyWolf
</code></pre><p>But honestly, I just use <code>-m</code> :) Anywho, this diversion into <code>PYTHONPATH</code> & module resolution wasn't too relevant, still useful to understand, back to the main content now :)</p><h2><a aria-hidden=true class=anchor href=#transitioning-to-hatch id=heading-transitioning-to-hatch></a>Transitioning To Hatch</h2><p>You may have noticed something about our basic setup: it doesn't create a virtual environment :( This isn't a problem for our extremely simple <code>hello</code> and <code>goodbye</code> programs, but as soon as we need a single dependency outside the standard library, we'll be in trouble.</p><p>For the longest time, I just stuck with the ol' reliable <code>python3 -m venv .venv && source .venv/bin/activate && pip3 -r requirements.txt</code>; <a href=https://docs.python.org/3/library/venv.html>venv</a> is included in the standard library, so it'll Just Work™️ on any Python installation >= 3.3, which is to say, "all of them". There are a bazillion other tools to manage Python virtual environments, like <a href=https://virtualenv.pypa.io/en/latest/user_guide.html>virtualenv</a>, <a href=https://pipenv.pypa.io/en/latest/>pipenv</a>, <a href=https://www.anaconda.com/docs/getting-started/miniconda/main>conda</a>, <a href=https://docs.astral.sh/uv/>uv</a>, & more, but I have never needed their full power, hence, ol' reliable.</p><p>Now, however, with the goal of "creating packages to be built with Nix", we might want something a bit more powerful, something capable of creating Python packages. The modern way to do this is with the <a href=https://packaging.python.org/en/latest/guides/writing-pyproject-toml/>pyproject.toml</a> format, whose guide recommends <a href=https://hatch.pypa.io/latest/>Hatch</a> as the default build backend, so that's what I'll be using for the rest of this "tutorial".</p><p>First, let's make some more directories, and move some files around. Why we're doing this will become apparent shortly:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ for lib in shared hello goodbye; do
> mkdir -p $lib/src/$lib
> mv $lib/*.py $lib/src/$lib
> done
</code></pre><p>Then, let's define our pyproject.toml for these new folders:</p><pre class=syntax-highlighting><code class=language-toml><span class=comment># shared/pyproject.toml</span>
<span class=punctuation>[</span><span class=property>build-system</span><span class=punctuation>]</span>
<span class=property>requires</span> <span class=operator>=</span> <span class=punctuation>[</span><span class=string>"hatchling >= 1.26"</span><span class=punctuation>]</span>
<span class=property>build-backend</span> <span class=operator>=</span> <span class=string>"hatchling.build"</span>

<span class=punctuation>[</span><span class=property>project</span><span class=punctuation>]</span>
<span class=property>name</span> <span class=operator>=</span> <span class=string>"shared"</span>
<span class=property>version</span> <span class=operator>=</span> <span class=string>"0.1.0"</span></code></pre><pre class=syntax-highlighting><code class=language-toml><span class=comment># {hello,goodbye}/pyproject.toml</span>
<span class=punctuation>[</span><span class=property>build-system</span><span class=punctuation>]</span>
<span class=property>requires</span> <span class=operator>=</span> <span class=punctuation>[</span><span class=string>"hatchling >= 1.26"</span><span class=punctuation>]</span>
<span class=property>build-backend</span> <span class=operator>=</span> <span class=string>"hatchling.build"</span>

<span class=punctuation>[</span><span class=property>project</span><span class=punctuation>]</span>
<span class=property>name</span> <span class=operator>=</span> <span class=string>"hello"</span> <span class=comment># goodbye/pyproject.toml is identical, save this line</span>
<span class=property>version</span> <span class=operator>=</span> <span class=string>"0.1.0"</span>
<span class=property>dependencies</span> <span class=operator>=</span> <span class=punctuation>[</span>
  <span class=string>"shared @ {root:parent:uri}/shared"</span><span class=punctuation>,</span> <span class=comment># ":parent" is equivalent to "../", and can be stacked as many times as required</span>
<span class=punctuation>]</span>

<span class=punctuation>[</span><span class=property>tool</span><span class=punctuation>.</span><span class=property>hatch</span><span class=punctuation>.</span><span class=property>metadata</span><span class=punctuation>]</span>
<span class=property>allow-direct-references</span> <span class=operator>=</span> <span class=constant>true</span></code></pre><p>We end up with a directory structure that looks like this:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ tree
.
├── goodbye
│   ├── pyproject.toml
│   └── src
│       └── goodbye
│           └── __main__.py
├── hello
│   ├── pyproject.toml
│   └── src
│       └── hello
│           └── __main__.py
└── shared
    ├── pyproject.toml
    └── src
        └── shared
            └── __init__.py

10 directories, 6 files
</code></pre><p>We've created 3 separate "projects", and then put our files within each project in such a way that Hatch expects to be able to build a package from them.</p><p>So! Let us now create a virtual environment, build a package, and run it, all in one handy command:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ exit

$ nix-shell -p hatch tree
fetching path input 'path:/nix/store/1dwky0bis4bkl3qngsc6pmq902swa9b6-source'

[nix-shell:/tmp/blogpost]$ cd hello

[nix-shell:/tmp/blogpost/hello]$ hatch run -- python3 -m hello
  error: subprocess-exited-with-error

  × Preparing editable metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [56 lines of output]
      Traceback (most recent call last):
        File "/home/nixos/.local/share/hatch/env/virtual/hello/DMd9LDvE/hello/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 167, in prepare_metadata_for_build_editable
...
</code></pre><p><em><strong>AAAAAAAAAA</strong></em></p><pre class=syntax-highlighting><code>...
      ValueError: Unable to determine which files to ship inside the wheel using the following heuristics: https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection

      The most likely cause of this is that there is no directory that matches the name of your project (hello).

      At least one file selection option must be defined in the `tool.hatch.build.targets.wheel` table, see: https://hatch.pypa.io/latest/config/build/

      As an example, if you intend to ship a directory named `foo` that resides within a `src` directory located at the root of your project, you can define the following:

      [tool.hatch.build.targets.wheel]
      packages = ["src/foo"]
      [end of output]
...
</code></pre><p>aaaaa oh wait ok I think I know why this is happening: we forgot to provide an empty <code>__init__.py</code> file to appease the Python Gods. The Python Gods are displeased when a folder acts as a module & does not contain an <code>__init__.py</code> file, and we only avoided their wrath earlier because our earthly machinations provided no threat to their heavenly realm; now that we have dared snatch the fire that is "build tool that manages a virtual environment" from their palace, the only way to placate them is to fix our directory structure, <em>not</em> to torture our config file like their oracles suggested. Allow me to demonstrate:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ for lib in hello goodbye; do touch $lib/src/$lib/__init__.py; done

[nix-shell:/tmp/blogpost]$ tree
.
├── goodbye
│   ├── pyproject.toml
│   └── src
│       └── goodbye
│           ├── __init__.py
│           └── __main__.py
├── hello
│   ├── pyproject.toml
│   └── src
│       └── hello
│           ├── __init__.py
│           └── __main__.py
└── shared
    ├── pyproject.toml
    └── src
        └── shared
            └── __init__.py

10 directories, 8 files
</code></pre><p>And now, we should surely be able to run:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost/hello]$ hatch run -- python3 -m hello
/home/nixos/.local/share/hatch/env/virtual/hello/DMd9LDvE/hello/bin/python3: No module named hello
</code></pre><p>Ah, right, stale environment:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost/hello]$ hatch env prune

[nix-shell:/tmp/blogpost/hello]$ hatch run -- python3 -m hello
Hello, PolyWolf
</code></pre><p>Big success!!<sup class=footnote-ref><a data-footnote-ref href=#fn-errors id=fnref-errors>2</a></sup> 🐍, amirite? :P</p><h2><a aria-hidden=true class=anchor href=#time-to-nix-it-up id=heading-time-to-nix-it-up></a>Time To Nix It Up</h2><p>Referencing <a href=https://hachyderm.io/@leftpaddotpy>Jade</a>'s excellent resource on <a href=https://jade.fyi/blog/flakes-arent-real/>using Nix flakes the non-flake way</a>, let's set up a simple devshell<sup class=footnote-ref><a data-footnote-ref href=#fn-more-nix-shells id=fnref-more-nix-shells>3</a></sup>:</p><pre class=syntax-highlighting><code class=language-nix><span class=comment># flake.nix</span>
<span class=punctuation>{</span>
  <span class=variable>inputs</span> <span class=punctuation>=</span> <span class=punctuation>{</span>
    <span class=variable>nixpkgs</span><span class=punctuation>.</span><span class=variable>url</span> <span class=punctuation>=</span> <span class=string>"github:NixOS/nixpkgs/nixos-unstable"</span><span class=punctuation>;</span>
    <span class=variable>flake-utils</span><span class=punctuation>.</span><span class=variable>url</span> <span class=punctuation>=</span> <span class=string>"github:numtide/flake-utils"</span><span class=punctuation>;</span>
  <span class=punctuation>};</span>

  <span class=variable>outputs</span> <span class=punctuation>=</span> <span class=punctuation>{</span> <span class=variable>self</span><span class=punctuation>,</span> <span class=variable>nixpkgs</span><span class=punctuation>,</span> <span class=variable>flake-utils</span> <span class=punctuation>}</span>: <span class=variable>flake-utils</span><span class=punctuation>.</span><span class=variable>lib</span><span class=punctuation>.</span><span class=variable>eachDefaultSystem</span> <span class=punctuation>(</span><span class=variable>system</span>:
    <span class=keyword>let</span>
      <span class=variable>pkgs</span> <span class=punctuation>=</span> <span class=variable>import</span> <span class=variable>nixpkgs</span> <span class=punctuation>{</span> <span class=keyword>inherit</span> <span class=variable>system</span><span class=punctuation>;</span> <span class=punctuation>};</span>
    <span class=keyword>in</span>
      <span class=punctuation>{</span>
        <span class=variable>devShells</span><span class=punctuation>.</span><span class=variable>default</span> <span class=punctuation>=</span> <span class=variable>pkgs</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./shell.nix</span> <span class=punctuation>{</span> <span class=punctuation>};</span>
      <span class=punctuation>}</span>
  <span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><pre class=syntax-highlighting><code class=language-nix><span class=comment># shell.nix</span>
<span class=punctuation>{</span> <span class=variable>pkgs</span> <span class=punctuation>?</span> <span class=variable>import</span> <span class=string>&lt;nixpkgs></span> <span class=punctuation>{</span> <span class=punctuation>}</span> <span class=punctuation>}</span>:
<span class=variable>pkgs</span><span class=punctuation>.</span><span class=variable>mkShell</span> <span class=punctuation>{</span>
  <span class=variable>packages</span> <span class=punctuation>=</span> <span class=punctuation>(</span>
    <span class=keyword>with</span> <span class=variable>pkgs</span><span class=punctuation>;</span>
    <span class=punctuation>[</span>
      <span class=comment># The python virtual environments themselves are managed with hatch, no need for any other dependencies in this file! Unless you want to put like, language server stuff here ofc</span>
      <span class=variable>hatch</span>
    <span class=punctuation>]</span>
  <span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><p>And for this next part, it would be very useful for to do a bit of reading on Nix before continuing; I recommend the nix.dev tutorials on <a href=https://nix.dev/tutorials/callpackage><code>callPackage</code></a> + <a href=https://nix.dev/tutorials/working-with-local-files>local files</a>, the NixOS Wiki page on <a href=https://wiki.nixos.org/wiki/Overlays>Overlays</a>, & all the basic Nix syntax ones. Otherwise, this will just be blind copy-pasting:</p><pre class=syntax-highlighting><code class=language-nix><span class=comment># shared/package.nix</span>
<span class=punctuation>{</span>
  <span class=variable>lib</span><span class=punctuation>,</span>
  <span class=variable>buildPythonPackage</span><span class=punctuation>,</span>
  <span class=variable>hatchling</span><span class=punctuation>,</span><span class=variable>
</span><span class=punctuation>}</span><span class=variable>:
</span><span class=variable>buildPythonPackage</span><span class=variable> </span><span class=punctuation>{</span><span class=variable>
  </span><span class=variable>pname</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=string>"shared"</span><span class=punctuation>;</span><span class=variable>
  </span><span class=variable>version</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=string>"0.1.0"</span><span class=punctuation>;</span><span class=variable>

  </span><span class=variable>src</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=variable>lib</span><span class=punctuation>.</span><span class=variable>fileset</span><span class=punctuation>.</span><span class=variable>toSource</span><span class=variable> </span><span class=punctuation>{</span><span class=variable>
    </span><span class=variable>root</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=string>./.</span><span class=punctuation>;</span><span class=variable>
    </span><span class=variable>fileset</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=variable>lib</span><span class=punctuation>.</span><span class=variable>fileset</span><span class=punctuation>.</span><span class=variable>gitTracked</span><span class=variable> </span><span class=string>./.</span><span class=punctuation>;</span><span class=variable>
  </span><span class=punctuation>};</span><span class=variable>

  </span><span class=variable>pyproject</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=variable>true</span><span class=punctuation>;</span><span class=variable>
  </span><span class=variable>build-system</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=punctuation>[</span><span class=variable> </span><span class=variable>hatchling</span><span class=variable> </span><span class=punctuation>];</span><span class=variable>
</span><span class=punctuation>}</span></code></pre><pre class=syntax-highlighting><code class=language-nix><span class=comment># {hello,goodbye}/package.nix</span>
<span class=punctuation>{</span>
  <span class=variable>lib</span><span class=punctuation>,</span>
  <span class=variable>buildPythonPackage</span><span class=punctuation>,</span>
  <span class=variable>hatchling</span><span class=punctuation>,</span>
  <span class=variable>myproject-shared</span><span class=punctuation>,</span> <span class=comment># Here, we take the package defined by shared/package.nix as a dependency, to be hooked up later</span><span class=variable>
</span><span class=punctuation>}</span><span class=variable>:
</span><span class=variable>buildPythonPackage</span><span class=variable> </span><span class=punctuation>{</span><span class=variable>
  </span><span class=variable>pname</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=string>"hello"</span><span class=punctuation>;</span><span class=variable> </span><span class=comment># goodbye/package.nix is identical, save this line</span><span class=variable>
  </span><span class=variable>version</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=string>"0.1.0"</span><span class=punctuation>;</span><span class=variable>

  </span><span class=variable>src</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=variable>lib</span><span class=punctuation>.</span><span class=variable>fileset</span><span class=punctuation>.</span><span class=variable>toSource</span><span class=variable> </span><span class=punctuation>{</span><span class=variable>
    </span><span class=variable>root</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=string>./.</span><span class=punctuation>;</span><span class=variable>
    </span><span class=variable>fileset</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=variable>lib</span><span class=punctuation>.</span><span class=variable>fileset</span><span class=punctuation>.</span><span class=variable>gitTracked</span><span class=variable> </span><span class=string>./.</span><span class=punctuation>;</span><span class=variable>
  </span><span class=punctuation>};</span><span class=variable>

  </span><span class=variable>pyproject</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=variable>true</span><span class=punctuation>;</span><span class=variable>
  </span><span class=variable>build-system</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=punctuation>[</span><span class=variable> </span><span class=variable>hatchling</span><span class=variable> </span><span class=punctuation>];</span><span class=variable>

  </span><span class=variable>propagatedBuildInputs</span><span class=variable> </span><span class=punctuation>=</span><span class=variable> </span><span class=punctuation>[</span><span class=variable> </span><span class=variable>myproject-shared</span><span class=variable> </span><span class=punctuation>];</span><span class=variable> </span><span class=comment># Here, we declare how exactly we depend on the package defined by shared/package.nix; we'll get into what this means later</span><span class=variable>
</span><span class=punctuation>}</span></code></pre><p>Whoa that's it!! For the Nixpkgs-style files, at least. Yeah, turns out using Hatch took care of most of the hard part of packaging, the rest is built-in to Nixpkgs itself. We do still have to repeat the dependencies of each project outside of the pyproject.toml<sup class=footnote-ref><a data-footnote-ref href=#fn-repeat-dependencies id=fnref-repeat-dependencies>4</a></sup>, which is a bummer, but oh well the rest of the file is quite small which is good, thank u Nixpkgs</p><p>With all these files in place, our tree now looks like:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ tree
.
├── flake.lock
├── flake.nix
├── goodbye
│   ├── package.nix
│   ├── pyproject.toml
│   └── src
│       └── goodbye
│           ├── __init__.py
│           └── __main__.py
├── hello
│   ├── package.nix
│   ├── pyproject.toml
│   └── src
│       └── hello
│           ├── __init__.py
│           └── __main__.py
├── shared
│   ├── package.nix
│   ├── pyproject.toml
│   └── src
│       └── shared
│           └── __init__.py
└── shell.nix

10 directories, 14 files
</code></pre><p>Next, we have to wire all these packages up inside our <code>flake.nix</code> so we can actually build them. These are fairly unusual packages, turns out; they use a top-level <code>buildPythonPackage</code> variable that doesn't exist in a normal Nixpkgs <code>callPackage</code>. This aligns with the <a href=https://ryantm.github.io/nixpkgs/languages-frameworks/python/>Nixpkgs manual</a>: Python packages are special, because they could be built for multiple versions of the interpreter, and this is the easiest way to do that without having to do something even crazier w/ overrides.</p><p>Anyways, here's our updated <code>flake.nix</code>, then an explanation:</p><pre class=syntax-highlighting><code class=language-nix><span class=comment># flake.nix</span>
<span class=punctuation>{</span>
  <span class=variable>inputs</span> <span class=punctuation>=</span> <span class=punctuation>{</span>
    <span class=variable>nixpkgs</span><span class=punctuation>.</span><span class=variable>url</span> <span class=punctuation>=</span> <span class=string>"github:NixOS/nixpkgs/nixos-unstable"</span><span class=punctuation>;</span>
    <span class=variable>flake-utils</span><span class=punctuation>.</span><span class=variable>url</span> <span class=punctuation>=</span> <span class=string>"github:numtide/flake-utils"</span><span class=punctuation>;</span>
  <span class=punctuation>};</span>

  <span class=variable>outputs</span> <span class=punctuation>=</span> <span class=punctuation>{</span> <span class=variable>self</span><span class=punctuation>,</span> <span class=variable>nixpkgs</span><span class=punctuation>,</span> <span class=variable>flake-utils</span> <span class=punctuation>}</span>: <span class=variable>flake-utils</span><span class=punctuation>.</span><span class=variable>lib</span><span class=punctuation>.</span><span class=variable>eachDefaultSystem</span> <span class=punctuation>(</span><span class=variable>system</span>:
    <span class=keyword>let</span>
      <span class=variable>pkgs</span> <span class=punctuation>=</span> <span class=variable>import</span> <span class=variable>nixpkgs</span> <span class=punctuation>{</span> <span class=keyword>inherit</span> <span class=variable>system</span><span class=punctuation>;</span> <span class=punctuation>};</span>
      <span class=variable>python3</span> <span class=punctuation>=</span> <span class=variable>pkgs</span><span class=punctuation>.</span><span class=variable>python3</span><span class=punctuation>.</span><span class=variable>override</span> <span class=punctuation>{</span>
        <span class=variable>packageOverrides</span> <span class=punctuation>=</span> <span class=variable>final</span>: <span class=variable>prev</span>: <span class=punctuation>{</span>
          <span class=variable>myproject-shared</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./shared/package.nix</span> <span class=punctuation>{</span> <span class=punctuation>};</span>
          <span class=variable>myproject-hello</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./hello/package.nix</span> <span class=punctuation>{</span>
            <span class=variable>myproject-shared</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>myproject-shared</span><span class=punctuation>;</span>
          <span class=punctuation>};</span>
          <span class=variable>myproject-goodbye</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./goodbye/package.nix</span> <span class=punctuation>{</span>
            <span class=variable>myproject-shared</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>myproject-shared</span><span class=punctuation>;</span>
          <span class=punctuation>};</span>
        <span class=punctuation>};</span>
      <span class=punctuation>};</span>
    <span class=keyword>in</span>
    <span class=punctuation>{</span>
      <span class=variable>packages</span> <span class=punctuation>=</span> <span class=punctuation>{</span> <span class=keyword>inherit</span> <span class=variable>python3</span><span class=punctuation>;</span> <span class=punctuation>};</span>
      <span class=variable>devShells</span><span class=punctuation>.</span><span class=variable>default</span> <span class=punctuation>=</span> <span class=variable>pkgs</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./shell.nix</span> <span class=punctuation>{</span> <span class=punctuation>};</span>
    <span class=punctuation>}</span>
  <span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><p>Oh wow that's dense! Let's split it up a bit:</p><pre class=syntax-highlighting><code class=language-nix><span class=keyword>let</span>
  <span class=variable>python3</span> <span class=punctuation>=</span> <span class=variable>pkgs</span><span class=punctuation>.</span><span class=variable>python3</span><span class=punctuation>.</span><span class=variable>override</span> <span class=punctuation>{</span>
    <span class=comment># ...</span>
  <span class=punctuation>};</span>
<span class=keyword>in</span>
<span class=punctuation>{</span>
  <span class=variable>packages</span> <span class=punctuation>=</span> <span class=punctuation>{</span> <span class=keyword>inherit</span> <span class=variable>python3</span><span class=punctuation>;</span> <span class=punctuation>};</span>
<span class=punctuation>}</span></code></pre><p>This declares a new <code>python3</code> package in our Flake, defined as an override of the base <code>python3</code> package, whose final <code>pkgs</code> attribute will contain our custom packages. And how do we declare those? With the <a href=https://nixos.wiki/wiki/Overlays>overlay</a> pattern!</p><pre class=syntax-highlighting><code class=language-nix><span class=variable>packageOverrides</span> <span class=punctuation>=</span> <span class=variable>final</span>: <span class=variable>prev</span>: <span class=punctuation>{</span>
  <span class=variable>myproject-shared</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./shared/package.nix</span> <span class=punctuation>{</span> <span class=punctuation>};</span>
  <span class=variable>myproject-hello</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./hello/package.nix</span> <span class=punctuation>{</span>
    <span class=variable>myproject-shared</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>myproject-shared</span><span class=punctuation>;</span>
  <span class=punctuation>};</span>
  <span class=variable>myproject-goodbye</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./goodbye/package.nix</span> <span class=punctuation>{</span>
    <span class=variable>myproject-shared</span> <span class=punctuation>=</span> <span class=variable>final</span><span class=punctuation>.</span><span class=variable>myproject-shared</span><span class=punctuation>;</span>
  <span class=punctuation>};</span>
<span class=punctuation>};</span></code></pre><p>Each package that we want to add gets <code>callPackage</code>-ed<sup class=footnote-ref><a data-footnote-ref href=#fn-final-callPackage id=fnref-final-callPackage>5</a></sup>, with its dependencies injected from the "final" resolved set of packages. This is just the Nix(pkgs)-y way of doing dependency tracking such that any dependency can be overridden at arbitrarily deep levels. Fortunately, we're just adding new packages, so we don't have to think <em>too</em> hard about that complexity here.</p><p>With that all in place, let's try to build one of our packages:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ nix build .#python3.pkgs.myproject-hello

[nix-shell:/tmp/blogpost]$ ls result
lib  nix-support

[nix-shell:/tmp/blogpost]$ ls result/lib/python3*/site-packages
hello  hello-0.1.0.dist-info
</code></pre><p>Huh! We might have expected a <code>shared</code> module to appear here too, given we declared it as a dependency. I guess it doesn't do those just yet... Maybe it'll be complete in the main package?</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ nix build .#python3

[nix-shell:/tmp/blogpost]$ ls result
bin  include  lib  nix-support  share

[nix-shell:/tmp/blogpost]$ cd result/bin

[nix-shell:/tmp/blogpost/result/bin]$ ./python3 -m hello
/nix/store/kjvgj2n3yn70hmjifg6y0bk9m4rf7jba-python3-3.12.10/bin/python3: No module named hello
</code></pre><p>No, that works even less! I guess that makes sense, it's not like the default installation of python3 comes with <em>every Python package ever built</em> by default, we'll need to do something to explicitly install those somehow. But this begs the question: how <em>do</em> Nixpkgs Python dependencies work, anyways?</p><h3><a aria-hidden=true class=anchor href=#she-propagate-on-my-build-input-till-i-fixed-point id=heading-she-propagate-on-my-build-input-till-i-fixed-point></a>She Propagate On My Build Input Till I Fixed Point</h3><p>Referencing <a href=https://ryantm.github.io/nixpkgs/languages-frameworks/python/#buildpythonpackage-parameters>the reference</a>:</p><blockquote><ul><li><code>nativeBuildInputs ? []</code>: Build-time only dependencies. Typically executables as well as the items listed in <code>setup_requires</code>.</li><li><code>buildInputs ? []</code>: Build and/or run-time dependencies that need to be compiled for the host machine. Typically non-Python libraries which are being linked.</li><li><code>nativeCheckInputs ? []</code>: Dependencies needed for running the <code>checkPhase</code>. These are added to <code>nativeBuildInputs</code> when <code>doCheck = true</code>. Items listed in <code>tests_require</code> go here.</li><li><code>propagatedBuildInputs ? []</code>: Aside from propagating dependencies, <code>buildPythonPackage</code> also injects code into and wraps executables with the paths included in this list. Items listed in <code>install_requires</code> go here.</li></ul></blockquote><p>And summarizing in simpler English: use <code>propagatedBuildInputs</code> for Python library dependencies. Okie dokie! I'm sure there are various reasons for this, but we did that earlier so we're on the right track.</p><p>But wait a minute, doing a normal <code>nix build</code> did <em>not</em> propagate this build input into the <code>result</code> directory. What gives? This confused me for a long while<sup class=footnote-ref><a data-footnote-ref href=#fn-nixpkgs-confusing id=fnref-nixpkgs-confusing>6</a></sup>, until I started looking at things from the perspective of a C compiler.</p><p>Basically: Nix was originally meant for packaging C code, so a lot of its semantics make more sense when viewed that way. Packages put in <code>buildInputs</code> will be built, put into <code>/nix/store</code>, their paths exposed in certain environment variables, and then it's up to whatever linker to actually hard-code those paths into the final executable. However, there is no "final executable" at the Python library level, hence why we put the dependencies into <code>propagatedBuildInputs</code> so they'll be available by the time we get around to creating a Python environment. A Python environment can be thought of like an executable in this framework; a script can "link" (literally, create symlinks to) all specified packages into the directory structure Python expects.</p><p>Quite fortunately, our goal of "link a Python package & all of its dependencies into a functioning Python environment" already has a simple solution:</p><pre class=syntax-highlighting><code class=language-nix><span class=comment># flake.nix</span>
<span class=comment># ...</span>
      in
      <span class=punctuation>{</span>
        <span class=variable>packages</span> <span class=punctuation>=</span> <span class=punctuation>{</span>
          <span class=keyword>inherit</span> <span class=variable>python3</span><span class=punctuation>;</span>
          <span class=variable>python3-myproject-hello</span> <span class=punctuation>=</span> <span class=variable>python3</span><span class=punctuation>.</span><span class=variable>withPackages</span> <span class=punctuation>(</span><span class=variable>ps</span>: <span class=punctuation>[</span> <span class=variable>ps</span><span class=punctuation>.</span><span class=variable>myproject-hello</span> <span class=punctuation>]);</span>
          <span class=variable>python3-myproject-goodbye</span> <span class=punctuation>=</span> <span class=variable>python3</span><span class=punctuation>.</span><span class=variable>withPackages</span> <span class=punctuation>(</span><span class=variable>ps</span>: <span class=punctuation>[</span> <span class=variable>ps</span><span class=punctuation>.</span><span class=variable>myproject-goodbye</span> <span class=punctuation>]);</span>
        <span class=punctuation>};</span>
        <span class=variable>devShells</span><span class=punctuation>.</span><span class=variable>default</span> <span class=punctuation>=</span> <span class=variable>pkgs</span><span class=punctuation>.</span><span class=variable>callPackage</span> <span class=string>./shell.nix</span> <span class=punctuation>{</span> <span class=punctuation>};</span>
      <span class=punctuation>}</span>
<span class=comment># ...</span></code></pre><p>This <code>python3.withPackages</code> function does exactly what we need!! Magic. Now, when we build & run:</p><pre class=syntax-highlighting><code>[nix-shell:/tmp/blogpost]$ nix build .#python3-myproject-hello

[nix-shell:/tmp/blogpost]$ cd result/bin

[nix-shell:/tmp/blogpost/result/bin]$ ./python3 -m hello
Hello, PolyWolf
</code></pre><p>It works! Yay!!</p><h2><a aria-hidden=true class=anchor href=#drawbacks id=heading-drawbacks></a>Drawbacks</h2><p>Now, by no means do I intend to represent that this approach is the perfect way of doing things with Python + Nix. Far from it, really. Here are some of rough edges I've run into in this workflow that I'd like to eventually sand off:</p><ul><li><code>shared</code>, <code>hello</code>, and <code>goodbye</code> have to live in those strange <code>$lib/src/$lib</code> directories. This is solely because pyproject.toml doesn't have good multi-package support; Nix itself is flexible enough, but I am relying on pyproject.toml to make the Nix packaging easier.</li><li>As mentioned earlier, we have to specify all dependencies twice: once in <code>pyproject.toml</code>, and once in <code>package.nix</code>, which is sad. Also, if there are any version constraints in <code>pyproject.toml</code>, those will effectively break <code>package.nix</code> because Nixpkgs only contains one version of each package.</li><li>If <code>shared</code> ever changes, the hatch environments for <code>hello</code> and <code>goodbye</code> will need to be deleted and re-recreated. Hatch itself seems to have a "development mode" where changed to the current project are reflected in the virtual environment, but it doesn't seem like that applies to this multi-project usecase.</li></ul><p>Still, this is just about good enough for my project, so I think I will stop here. All this Nix packaging is just so I can write a <a href=https://github.com/p0lyw0lf/infrastructure/tree/d430367a2e3f9aa95783314451d1509446ea5e16/nixos>NixOS module</a> and finally start deploying my site the Cool way (<code>nixos-rebuild switch --flake .#devbox --target-host devbox</code>) instead of the Boring way (<code>ssh devbox 'cd crossposter; git pull; systemctl --user restart rc.wolfgirl.dev'</code>). Join me next time for that, I guess?? Assuming I don't get sidetracked on something else first lol</p><section class=footnotes data-footnotes><ol><li id=fn-python-vs-js><p>From what I understand, this is a much more common pattern in the Javascript & Rust worlds, where the multi-module build tooling is actually somewhat decent. No matter, we press onwards regardless of our fate here. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-python-vs-js>↩</a></p></li><li id=fn-errors><p>I hope showing these errors is useful, by the way. I know most tutorials just show the happy path, but I think it's also important to show the common error paths too, and ways off of them back onto the happy path. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-errors>↩</a></p></li><li id=fn-more-nix-shells><p>For longer reads on Nix devshells that go way too into the details (/pos) I highly recommend fasterthanlime's post series on the <a href=https://fasterthanli.me/series/building-a-rust-service-with-nix/part-9>non-flake way</a> and the <a href=https://fasterthanli.me/series/building-a-rust-service-with-nix/part-10>flake way</a>. These are more targeted at Rust devshells and also maybe slightly out-of-date but that's ok the basics are still very pertinent & Amos is a very good writer. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-more-nix-shells>↩</a></p></li><li id=fn-repeat-dependencies><p>I'm sure a sufficiently motivated nix-er could figure out a way to parse the pyproject.toml at evaluation time, but I am not that. Oh well! <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-repeat-dependencies>↩</a></p></li><li id=fn-final-callPackage><p>Expert Nix users will note the <code>final.callPackage</code> calls are a bit strange, when usually overlays use <code>prev.callPackage</code> for various reasons beyond my ken. For another reason even further beyond my ken, <code>prev.callPackage</code> doesn't exist in this place, so we use <code>final.callPackage</code> instead ya <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-final-callPackage>↩</a></p></li><li id=fn-nixpkgs-confusing><p>Pro tip: Don't read the <a href=https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-dependencies-propagated>relevant section of the nixpkgs manual</a> when first learning how dependencies work. Only served to confuse me more tbh <a aria-label="Back to reference 6" class=footnote-backref data-footnote-backref data-footnote-backref-idx=6 href=#fnref-nixpkgs-confusing>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Some Albums I Am Looking Forward To (#4)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-04-30-some-albums-i-am-looking-forward-to/" />
      <id>https://wolfgirl.dev/blog/2025-04-30-some-albums-i-am-looking-forward-to/</id>
      <published>2025-04-30T13:48:10Z</published>
      <updated>2025-04-30T13:48:10Z</updated>
      <summary><![CDATA[## Flume & JPEGMAFIA - We Live In A Society  ![Album cover, presumably JPEGMAFIA & Flume hands in a handshake over a pure white backgro...]]></summary>
      <content type="html"><![CDATA[<h2><a aria-hidden=true class=anchor href=#flume--jpegmafia---we-live-in-a-society id=heading-flume--jpegmafia---we-live-in-a-society></a>Flume & JPEGMAFIA - We Live In A Society</h2><p><img alt="Album cover, presumably JPEGMAFIA & Flume hands in a handshake over a pure white background" src=https://static.wolfgirl.dev/polywolf/blog/019686c7-873d-7ee0-85a7-07f65a3561b2/IMG_9549.png></p><p>I found out this was coming due to a <a href=https://youtu.be/lhVeE3N3Rcw>teaser video for the first track</a>. It's a bit crudely animated, but I think that was an intentional artistic choice to capture the transition from "meandering jam sesh with crazy discordant electronic noise" to "immaculately layered harmonics extracted from the best noise & ideas". I keep coming back to it for two reasons: the song they get to near the end is quite catchy & exactly my thing yes, but also I really like seeing that raw artistic process, really captivating even if it may have been a simulacra.</p><p>An <a href=http://youtube.com/post/UgkxB5iH1tU43LpjjNcsweom9kAYpz1QhYNN>accompanying text post</a> lists the set at 4 tracks long, dropping May 2nd on presumably all major streaming services. That's quite soon, exciting!! All Flume stuff is worth a listen imo, even if I don't end up liking it :)</p><h2><a aria-hidden=true class=anchor href=#shibayanrecords---toho-bossa-nova-14 id=heading-shibayanrecords---toho-bossa-nova-14></a>Shibayanrecords - TOHO BOSSA NOVA 14</h2><p><img alt="Sakuya Izayoi from Touhou Project basking in a white-and-red moonlight, as the album cover" src=https://static.wolfgirl.dev/polywolf/blog/019686c7-873d-7ee0-85a7-07f65a3561b2/IMG_9550.webp></p><p>This is a very conventional release, both for Shibayan and for Japanese music artists in general: <a href=https://youtu.be/ZQtK3WALRJI>Crossfade</a> available as a whole-album sampler a week before release on May 5th, available for immediate preorder on <a href="https://www.melonbooks.co.jp/detail/detail.php?product_id=2932209">melonbooks</a> if you live in Japan, and streaming internationally some indefinite time later.</p><p>Shibayan always releases a TOHO BOSSA NOVA album in May, every year for 14 years running now I guess. <a href=https://wolfgirl.dev/blog/2025-02-16-some-albums-i-ve-been-listening-to-a-lot-recently/>I've mentioned before</a>, and while #2 is so far my favorite, it is nice to have a yearly tradition to listen to something fresh yet familiar, nostalgic yet novel. Excited for this one too, hopefully I can hear it some time in May.</p><hr><p>Usually I find a third album for things like this, but heh it's really only these two. Instead, here's two more already released albums I have not listened instead:</p><ul><li><a href=https://neoncityrecords.bandcamp.com/album/virtues-ep-deluxe>Aritus x FIBRE - Virtues EP (Deluxe)</a>: Story goes that this was produced in they heyday of future funk, when high schoolers didn't quite know about music licensing, so now they finally figured out how to get the licensing for this remaster, neat!</li><li><a href=https://greyl.bandcamp.com/album/singlas-chronicle-01>greyl - SINGALS CHRONICLE 01</a>: u already know how i feel about greyl. i def gotta get this</li></ul><p>Also, feel free to reach out with album recommendations of your own!! Comment links should be at the bottom of the page assuming my software works correctly. I cannot possibly keep up with all the cool music coming out, so hearing things from people directly helps filter that down considerably.</p><p>That's it for now, until next time!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: Holding the Bag]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-04-25-rechost-holding-the-bag/" />
      <id>https://wolfgirl.dev/blog/2025-04-25-rechost-holding-the-bag/</id>
      <published>2025-04-25T17:46:03Z</published>
      <updated>2025-04-25T17:46:03Z</updated>
      <summary><![CDATA[<https://serd.es/2025/04/17/Holding-the-Bag.html>  The author does a lot of neat stuff with hardware imaging on their mastodon account,...]]></summary>
      <content type="html"><![CDATA[<p><a href=https://serd.es/2025/04/17/Holding-the-Bag.html>https://serd.es/2025/04/17/Holding-the-Bag.html</a></p><p>The author does a lot of neat stuff with hardware imaging on their mastodon account, <a href=https://ioc.exchange/@azonenberg>@azonenberg@ioc.exchange</a>, which is very cool, and I've been following for a while. Just got around to reading this piece; it's fun sci-fi, I enjoyed it a lot!</p><details><summary>slight spoilers for why</summary> It is, in large part, because the entire thing is a setup for the joke-ish reveal at the end. The contrivances abound (why Earth, specifically? and how come the protagonist has no agency?), but there are so many other silly details that make me smile & think "ah, someone who loves computers/science wrote this", that it's all good. Also the author's specialty, computational imaging, makes a star play and I think that's great :3</details>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Deriving `Functor` in Rust]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-04-14-deriving-functor-in-rust/" />
      <id>https://wolfgirl.dev/blog/2025-04-14-deriving-functor-in-rust/</id>
      <published>2025-04-14T15:37:56Z</published>
      <updated>2025-04-14T15:37:56Z</updated>
      <summary><![CDATA[Wow it's been a hot minute! I kept meaning to write about this, but each time I sat down, I came up with a new idea that I just _had_ to...]]></summary>
      <content type="html"><![CDATA[<p>Wow it's been a hot minute! I kept meaning to write about this, but each time I sat down, I came up with a new idea that I just <em>had</em> to try out. At this point, I have tried enough things, and am reasonably confident all my explanations will be correct. So, without further ado,</p><h2><a aria-hidden=true class=anchor href=#problem-statement id=heading-problem-statement></a>Problem Statement</h2><p>Given:</p><ul><li>A finite set of <code>struct</code>s/<code>enum</code>s (henceforth referred to as "items"), possibly generic</li></ul><p>Create:</p><ul><li>A way to, for each item in the set, recursively transform any other item, including inner generic parameters, if possible.</li></ul><p>Let's take a look at a simple example:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=property>val</span><span class=punctuation>:</span> <span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
    <span class=property>next</span><span class=punctuation>:</span> <span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>Box</span><span class=punctuation>&lt;</span><span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>>></span>
<span class=punctuation>}</span>
<span class=keyword>struct</span> <span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>(</span><span class=type>T</span><span class=punctuation>);</span></code></pre><p>Then, given a <code>ListNode&lt;T></code>, we'd like to be able to turn it into a <code>ListNode&lt;U></code>, just by specifying how to turn <code>Val&lt;T></code> into <code>Val&lt;U></code>.</p><h2><a aria-hidden=true class=anchor href=#approach-1-write-a-traversal-manually id=heading-approach-1-write-a-traversal-manually></a>Approach 1: Write a traversal manually</h2><pre class=syntax-highlighting><code class=language-rust><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>transform</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>)</span> -> <span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>)</span> -> <span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>ListNode</span> <span class=punctuation>{</span>
            <span class=property>val</span><span class=punctuation>:</span> <span class=function>f</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>.</span><span class=property>val</span><span class=punctuation>),</span>
            <span class=property>next</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>next</span><span class=punctuation>.</span><span class=function>map</span><span class=punctuation>(</span>|n| <span class=type>Box</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>((</span><span class=operator>*</span>n<span class=punctuation>).</span><span class=function>transform</span><span class=punctuation>(</span>f<span class=punctuation>))),</span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Great! Nice & simple. However, manually mapping through the <code>Option</code> and <code>Box</code> is a bit tedious, especially if we want to write other, similar transforms. Let's see if we can solve that.</p><h2><a aria-hidden=true class=anchor href=#approach-2-functor-time id=heading-approach-2-functor-time></a>Approach 2: <code>Functor</code> time!!</h2><p>In my <a href=https://wolfgirl.dev/blog/2024-11-24-a-novel-idea-about-functor-in-rust/>last post on this subject</a>, I covered this crazy wacky trait I called <code>Functor</code>, invented specifically to do these sorts of transforms. Here's the trait definition<sup class=footnote-ref><a data-footnote-ref href=#fn-functor id=fnref-functor>1</a></sup>:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>;</span>
<span class=punctuation>}</span></code></pre><p>And here's how it's used (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=986293cbdec0b24e8f2f7a3f3406067e">playground link</a>), with doc comments explaining what each part means:</p><pre class=syntax-highlighting><code class=language-rust><span class=comment>/// There are 4 types involved in any Functor implementation: {Input, Output} x {Inner, Container}.
/// What a Functor implementation means is, given a function InputInner -> OutputInner, you can write a function InputContainer -> OutputContainer.
/// Writing the full name of each type is a bit hard, so we have shorthand:
/// + InputInner = Input (Val&lt;T>)
/// + OutputInner = Output (Val&lt;U>)
/// + InputContainer = Self (ListNode&lt;T>)
/// + OutputContainer = Mapped (ListNode&lt;U>)
</span><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>></span> <span class=keyword>for</span> <span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=keyword>mut</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>)</span> -> <span class=type>Val</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>)</span> -> <span class=type>ListNode</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>ListNode</span> <span class=punctuation>{</span>
            <span class=property>val</span><span class=punctuation>:</span> <span class=function>f</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>.</span><span class=property>val</span><span class=punctuation>),</span>
            <span class=property>next</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>next</span><span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span><span class=operator>&</span><span class=keyword>mut</span> f<span class=punctuation>),</span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>
<span class=comment>/// Given all types T that can be mapped over, allow Option&lt;T> to be mapped over in the same way, automatically un/re-wrapping the Some case as required
</span><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>Output</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span>
<span class=keyword>where</span>
    <span class=type>T</span><span class=punctuation>:</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>></span>
<span class=punctuation>{</span>
	<span class=comment>/// Input is the type given as input to the internal transform. In this case, because we are transparent, we use T's input
</span>    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>T</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>;</span>
    <span class=comment>/// Mapped is the external type after the transform has been done. Because we are wrapping T as input, we also wrap T's mapped type as output.
</span>    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span>
        <span class=variable>self</span><span class=punctuation>,</span> <span class=comment>//: Option&lt;T></span>
        <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>T</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Output</span><span class=punctuation>,</span>
    <span class=punctuation>)</span> -> <span class=type>Option</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>Option</span><span class=punctuation>::</span><span class=function>map</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> |x| x<span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>))</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>

<span class=comment>/// The implementation for Box&lt;T> is very similar to that for Option&lt;T>, included for completeness.
</span><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>Output</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span>
<span class=keyword>where</span>
    <span class=type>T</span><span class=punctuation>:</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>>,</span>
<span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>T</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>Box</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span>
        <span class=variable>self</span><span class=punctuation>,</span>
        <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Output</span><span class=punctuation>,</span>
    <span class=punctuation>)</span> -> <span class=type>Box</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>Box</span><span class=punctuation>::</span><span class=function>new</span><span class=punctuation>((</span><span class=operator>*</span><span class=variable>self</span><span class=punctuation>).</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>))</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Neat! This way, if we're writing a lot of transforms, they'll be composable with existing container types like <code>Option</code> & <code>Box</code>, as well as any other container types we might try.</p><p>However, I'm still a bit sad about having to write these transforms by hand. Especially given the fact that, while <code>Functor</code> is nice to implement for wrapper types like <code>Option</code> and <code>Box</code>, it isn't as nice for "AST-like" types...</p><h3><a aria-hidden=true class=anchor href=#problems-with-ast-like-types id=heading-problems-with-ast-like-types></a>Problems With AST-like Types</h3><p>Consider what would happen if we had the following set of items:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>A</span> <span class=punctuation>{</span>
    <span class=property>b</span><span class=punctuation>:</span> <span class=type>B</span><span class=punctuation>,</span>
<span class=punctuation>}</span>
<span class=keyword>struct</span> <span class=type>B</span> <span class=punctuation>{</span>
    <span class=property>c</span><span class=punctuation>:</span> <span class=type>C</span><span class=punctuation>,</span>
<span class=punctuation>}</span>
<span class=keyword>struct</span> <span class=type>C</span><span class=punctuation>(</span><span class=type>i32</span><span class=punctuation>);</span></code></pre><p>Naturally, we'd want to call <code>a.fmap(|c: C| ...)</code>. But! There is no item <code>C</code> in any direct fields of <code>A</code>, only indirectly thru <code>b: B</code>. If we only cared about this one <code>fmap</code> call, we could write:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>C</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>A</span> <span class=punctuation>{</span>
	<span class=keyword>type</span> <span class=type>Input</span> = <span class=type>C</span><span class=punctuation>;</span>
	<span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>A</span><span class=punctuation>;</span>
	<span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>C</span><span class=punctuation>)</span> -> <span class=type>C</span><span class=punctuation>)</span> -> <span class=type>A</span> <span class=punctuation>{</span>
		<span class=type>A</span> <span class=punctuation>{</span> <span class=property>b</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>)</span> <span class=punctuation>}</span>
	<span class=punctuation>}</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>C</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>B</span> <span class=punctuation>{</span>
	<span class=keyword>type</span> <span class=type>Input</span> = <span class=type>C</span><span class=punctuation>;</span>
	<span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>B</span><span class=punctuation>;</span>
	<span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=keyword>mut</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>C</span><span class=punctuation>)</span> -> <span class=type>C</span><span class=punctuation>)</span> -> <span class=type>B</span> <span class=punctuation>{</span>
		<span class=type>B</span> <span class=punctuation>{</span> <span class=property>c</span><span class=punctuation>:</span> <span class=function>f</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>.</span><span class=property>c</span><span class=punctuation>)</span> <span class=punctuation>}</span>
	<span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Which is fine. But, you might eventually add an item <code>D</code> that you also want to map over from <code>A</code>. Then you'd have to write:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>D</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>A</span> <span class=punctuation>{</span> ... <span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>D</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>B</span> <span class=punctuation>{</span> ... <span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>D</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>C</span> <span class=punctuation>{</span> ... <span class=punctuation>}</span></code></pre><p>Apparently, the number of <code>Functor</code> implementations you have to write scales linearly with both the depth of the tree <em>and</em> the number of things you want to map over, so quadratically in total, which is quite bad.</p><p>Ideally, you'd like to just scale linearly with the size of the tree. Here's one approach for this:</p><pre class=syntax-highlighting><code class=language-rust><span class=comment>/// These "base impls" can all be easily generated with a macro
</span><span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>A</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>A</span> <span class=punctuation>{</span>
	<span class=keyword>type</span> <span class=type>Input</span> = <span class=type>A</span><span class=punctuation>;</span>
	<span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>A</span><span class=punctuation>;</span>
	<span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=keyword>mut</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>A</span><span class=punctuation>)</span> -> <span class=type>A</span><span class=punctuation>)</span> -> <span class=type>A</span> <span class=punctuation>{</span>
		<span class=function>f</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>)</span>
	<span class=punctuation>}</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>B</span> <span class=punctuation>{</span> ... <span class=punctuation>}</span>
<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>C</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>C</span> <span class=punctuation>{</span> ... <span class=punctuation>}</span>

<span class=comment>/// These "recursive impl"s are written manually, but that's OK, we only need one per item!
</span><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>A</span> <span class=keyword>where</span> <span class=type>B</span><span class=punctuation>:</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>Mapped</span>=<span class=type>B</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = &lt;<span class=type>B</span> <span class=keyword>as</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span>><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>A</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>T</span><span class=punctuation>)</span> -> <span class=type>A</span> <span class=punctuation>{</span>
        <span class=type>Self</span> <span class=punctuation>{</span>
            <span class=property>b</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>b</span><span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>),</span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>
<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>B</span> <span class=keyword>where</span> <span class=type>C</span><span class=punctuation>:</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>Mapped</span>=<span class=type>C</span><span class=punctuation>></span> <span class=punctuation>{</span> ... <span class=punctuation>}</span></code></pre><p>Seasoned Rust veterans may be able to spot the problem: <code>impl&lt;A> Functor&lt;A> for A</code> and <code>impl&lt;T> Functor&lt;T> for A</code> conflict! Never mind that <code>where</code> clause, the Rust compiler is sometimes very particular about preventing future conflicts too<sup class=footnote-ref><a data-footnote-ref href=#fn-conflicts id=fnref-conflicts>2</a></sup>, just in case <code>B: Functor&lt;A, Mapped=B></code> somehow.</p><p>If only there were a way to automatically write all those quadratic dumb-but-non-conflicting implementations... then it wouldn't matter that there's so many...<sup class=footnote-ref><a data-footnote-ref href=#fn-compiletimes id=fnref-compiletimes>3</a></sup></p><h2><a aria-hidden=true class=anchor href=#approach-3-use-a-proc_macro id=heading-approach-3-use-a-proc_macro></a>Approach 3: Use A <code>proc_macro</code></h2><p>Turns out, there's enough information in the item definitions alone to generate the traversals automatically. All we really need is:</p><ol><li>A list of all items considered a part of the AST</li><li>The type of each field in each item</li><li>A way of determining valid transforms to generate for each item</li><li>A way of generating each valid transform</li></ol><p>We need (1) because of the problem presented before: it's not possible to generate <code>Functor</code> impls for <em>arbitrary</em> types, so we must explicitly choose the ones we want to map over.</p><p>(2) and (4) are also fairly straightforward given Rust's de-facto introspection libraries, <a href=https://docs.rs/syn/latest/syn/>syn</a> & <a href=https://docs.rs/quote/latest/quote/>quote</a><sup class=footnote-ref><a data-footnote-ref href=#fn-dtolnay id=fnref-dtolnay>4</a></sup>. There are some subtleties when it comes to actually extracting the information we want (the Rust type grammar is so much...), but overall "get the type of a field" and "get the shape of an item" is not too hard. Same with generating the transform; once you have the information of "what type am I transforming" and "what fields should I transform for that type", emitting the code is fairly straightforward. You can <a href=https://github.com/p0lyw0lf/pwcc/blob/715eca08fc3e6acc5ba65030d096bf383c78c054/functional_macros/src/traits/functor.rs>take a look at my code</a> if you're curious, but I promise it's really boring.</p><p>(3), on the other hand, is much more difficult + interesting, and is a big reason I kept delaying this piece. Turns out re-creating the Rust trait implementation coherence semantics is pretty involved!!</p><h3><a aria-hidden=true class=anchor href=#what-even-is-a-valid-transformation id=heading-what-even-is-a-valid-transformation></a>What Even Is A Valid Transformation?</h3><p>Let's start out with the most basic criteria. Any valid implementation of <code>Functor&lt;B> for A</code> comes about because:</p><ol><li><code>A</code> has a field with type <code>B</code>, where "with type" looks inside any wrapper types like <code>Option</code>/<code>Box</code>, OR</li><li><code>A</code> has a field with type <code>C</code>, where <code>Functor&lt;B> for C</code> is a valid transform.</li></ol><p>We get the information for (1) when reading in the items initially. To get (2), we must turn to the horror that is graph algorithms in Rust. Get ready for some math!</p><h3><a aria-hidden=true class=anchor href=#1-the-lattice-step id=heading-1-the-lattice-step></a>1: The Lattice Step</h3><p>I call this a "lattice" because it encodes a "higher-than" relationship that's useful for what I'm doing. It's not a <em>true</em> lattice by many definitions of the word, but im the programmer so i get to name things badly ok</p><p>What this step looks like is, taking all edges matching <code>Functor&lt;C> for A</code> (denoted as <code>A -> C</code>) + <code>C -> B</code>, and collecting them into a new edge <code>A -> B</code>. This is so we can get all the possible implementations that would be covered by case (2) above. There are a few subtleties when it comes to unifying generic contexts, but like the mathematician I'm cosplaying as, I'll leave those for the appendix<sup class=footnote-ref><a data-footnote-ref href=#fn-unifying id=fnref-unifying>5</a></sup>.</p><p>However! We're not done yet. Creating all these new edges gives us all the <em>possible</em> implementations, but not all the <em>valid</em> ones. To do this, we need to filter out all invalid edges, so let's look at some ways an implementation (as defined by an edge) can be invalid.</p><h3><a aria-hidden=true class=anchor href=#2-filter-out-conflicting-generic-instantiations id=heading-2-filter-out-conflicting-generic-instantiations></a>2: Filter Out Conflicting Generic Instantiations</h3><p>Consider the following:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>A1</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=property>bt</span><span class=punctuation>:</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
    <span class=property>bu</span><span class=punctuation>:</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>,</span>
<span class=punctuation>}</span>
<span class=keyword>struct</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>(</span><span class=type>T</span><span class=punctuation>);</span></code></pre><p>If we try list out all the possible implementations, we get:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>></span> <span class=keyword>for</span> <span class=type>A1</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=punctuation>{</span> ... <span class=punctuation>}</span>
<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>></span> <span class=keyword>for</span> <span class=type>A1</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=punctuation>{</span> ... <span class=punctuation>}</span></code></pre><p>Note we have two implementations, because <code>B&lt;T></code> and <code>B&lt;U></code> are different types. But clearly, these implementations conflict! This is because <code>Functor&lt;B&lt;T>></code> and <code>Functor&lt;B&lt;U>></code> are <em>not</em> different traits. Without any preference for which implementation to keep, we discard both.</p><p>There is a similar conflict if we instead have:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>A2</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=property>bt</span><span class=punctuation>:</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
    <span class=property>bi</span><span class=punctuation>:</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>i32</span><span class=punctuation>>,</span>
<span class=punctuation>}</span></code></pre><p>Because <code>Functor&lt;B&lt;T>></code> <em>could</em> be the same trait as <code>Functor&lt;B&lt;i32>></code>, and without a "type inequality" bound (not possible to express in Rust iirc), we can't generate these. In this case, we just prefer the <code>Functor&lt;B&lt;i32>></code> implementation that only transforms <code>bi</code>, because without generic parameters, implementations can never conflict.</p><p>The algorithm for deciding if a pair of instantiations conflict is as follows:</p><ol><li>For each pair of arguments in order: <ol><li>If both are generic, they must be the same generic. Otherwise, there's a conflict.</li><li>If only one is generic, there's a conflict</li><li>If neither are generic, there's no conflict.</li></ol></li></ol><h3><a aria-hidden=true class=anchor href=#3-restrict-which-generic-parameters-are-transformed id=heading-3-restrict-which-generic-parameters-are-transformed></a>3: Restrict Which Generic Parameters Are Transformed</h3><p>This is a bit of a case of me being burned by making my <code>Functor</code> too complex, but screw it I <em>did</em> want that complexity. The situation is, if you have something like:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>A</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=property>b</span><span class=punctuation>:</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
    <span class=property>c</span><span class=punctuation>:</span> <span class=type>C</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
<span class=punctuation>}</span>
<span class=keyword>struct</span> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=property>c</span><span class=punctuation>:</span> <span class=type>C</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>,</span>
<span class=punctuation>}</span>
<span class=keyword>struct</span> <span class=type>C</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>(</span><span class=type>T</span><span class=punctuation>);</span></code></pre><p>And we try to write the "full" implementation for <code>A -> B</code> that transforms its generic parameter:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>U</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>></span> <span class=keyword>for</span> <span class=type>A</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>A</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>B</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>>)</span> -> <span class=type>B</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>>)</span> -> <span class=type>A</span><span class=punctuation>&lt;</span><span class=type>U</span><span class=punctuation>></span> <span class=punctuation>{</span>
        <span class=type>A</span> <span class=punctuation>{</span>
            <span class=property>b</span><span class=punctuation>:</span> <span class=function>f</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>.</span><span class=property>b</span><span class=punctuation>),</span>
            <span class=property>c</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>c</span><span class=punctuation>,</span> <span class=comment>// ERROR: expected C&lt;U>, got C&lt;T></span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>Oh no type error!! The reason it happens is because we can't transform the generic type of <code>c</code> even though we need to. There are a couple parts to this:</p><ol><li>How do we know we need to transform <code>c</code>? <ul><li>Because the generic context used by field <code>b</code>, and therefore the generic context transformed by <code>b</code>, is also used by field <code>c</code>.</li></ul></li><li>How do we know we cannot transform <code>c</code>? <ul><li>We look at all the outgoing edges from item <code>C</code>, and if there are none that would satisfy the transformation <code>C -> B</code> (for the same instantiated item <code>B</code> as is being considered for field <code>b</code>), then field <code>c</code> cannot be transformed.</li></ul></li></ol><p>Anyways, this is all just to check if we can transform the generic parameter <code>T</code> to <code>U</code>. If we can't, no worries, we can just generate a <code>Functor</code> implementation that doesn't transform <code>T</code> :) Like any sane person would do :)</p><h3><a aria-hidden=true class=anchor href=#what-is-the-correct-order-for-these-steps id=heading-what-is-the-correct-order-for-these-steps></a>What Is The Correct Order For These Steps?</h3><p>If you've been paying attention (no worries if not, it's a long one, you're almost there!), you may have noticed something: step 3 above look at outgoing edges for all nodes one level "below" the current. However, step 2 explicitly removes some edges from the graph. So! We must be smart about the order in which we look at nodes for step 3, so we don't end up keeping an edge <code>A -> B</code> only to later remove the edge <code>C -> B</code> that made keeping it possible.</p><p>I claim the correct traversal order is "reverse <a href=https://en.wikipedia.org/wiki/Topological_sorting>topological sort</a> order". (Whoopee, more graph algorithms in Rust!) Basically, we should start with nodes that don't point to anything (the "bottom"), then nodes that only point to previously explored nodes, and so on. This ensures we don't end up in that bad scenario.</p><p>However! The topological sort order is only defined for directed graphs with no loops. Our graph <em>can</em> have loops, and indeed, that's a big feature for this macro in general, being able to support trees & linked lists & such, which all have edges that look like <code>A -> A</code> or even <code>A -> B -> C -> A</code>. Are we out of luck?</p><p>Not yet! By first finding the <a href=https://en.wikipedia.org/wiki/Strongly_connected_component>strongly connected components</a> (omg more graph algorithms!!), we can do a topological sort on <em>those</em>, which are guaranteed to not have loops between them, and our traversal order will be good.</p><p>"But what about the order within a strongly connected component?", you may ask. Well, thanks to our lattice step earlier, every loop <code>A -> B -> C -> A</code> in the graph will become a clique, so if <code>A</code>, <code>B</code>, and <code>C</code> are in the same strongly connected component, <code>A -> B</code> & <code>C -> B</code> will always exist. Wow it's almost like I'm actually doing math!!</p><p>So, the correct order of steps is:</p><ol><li>Lattice step</li><li>Filter out conflicting instantiations</li><li>Topological sort</li><li>Restrict generic parameters</li></ol><p>And that's all there is to it! ;)</p><h2><a aria-hidden=true class=anchor href=#conclusion id=heading-conclusion></a>Conclusion</h2><p>At long last, we have reached the end of everything I know about automatically deriving a <code>Functor</code> implementation. These rules have worked out for me in practice, for <a href=https://github.com/p0lyw0lf/pwcc/blob/715eca08fc3e6acc5ba65030d096bf383c78c054/pwcc/src/parser.rs#L24-L164>two very</a> <a href=https://github.com/p0lyw0lf/pwcc/blob/715eca08fc3e6acc5ba65030d096bf383c78c054/pwcc/src/codegen.rs#L19-L111>large ASTs</a>. There are more things I know, like deriving <a href=https://github.com/p0lyw0lf/pwcc/blob/715eca08fc3e6acc5ba65030d096bf383c78c054/functional/src/try_functor.rs#L8-L25>TryFunctor</a> and <a href=https://github.com/p0lyw0lf/pwcc/blob/715eca08fc3e6acc5ba65030d096bf383c78c054/functional/src/foldable.rs#L5-L22>Foldable</a> and <a href=https://github.com/p0lyw0lf/pwcc/blob/715eca08fc3e6acc5ba65030d096bf383c78c054/functional/src/test_visit.rs#L7-L50>Visit</a>, but those all use these same basic principles too. There are also many more things I do not know, like "what if a field contains a generic type but doesn't have one of the designated items?" or "how can I prevent mapping invalid field types, like <code>fn(T) -> U</code>" or "am I using <code>panic!</code> to handle errors too often", but those are all left for later/never. I do not recommend anyone else attempt to use this code btw, it is simply a dangerous toy for my own amusement, and it <em>will</em> blow up in ur face.</p><p>Anyways, thanks for reading, see u next time!! :3</p><section class=footnotes data-footnotes><ol><li id=fn-functor><p>If you look in <a href=https://github.com/p0lyw0lf/pwcc/blob/d40f72bfd97c8b95bda9e12ec9637c1d4d6fe56c/functional/src/functor.rs#L21-L25>my repository</a>, you'll see I actually use a couple modifications: (1) it takes in an <code>&mut impl FnMut</code>, and (2) it takes in an extra <code>RecursiveCall</code> parameter. These are for structures like binary trees, where (1) the same <code>f</code> can't be moved into both calls, and trying to <code>&mut</code> it generates an infinite sequence of <code>&mut</code> checks, and (2) controlling when <code>f</code> would be called on a parent node: before its children, after its children, or not calling into children at all. Anyways! Not important for this piece, all the other relevant details are the same. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-functor>↩</a></p></li><li id=fn-conflicts><p>As it turns out, if all of these implementations & items are in the same crate as <code>Functor</code>, with private visibility, this actually works out! (<a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=022e8efdd57f9085dd0729093a64f046">playground link</a>.) The compiler can prove that the implementations never conflict, and these will be the only implementations that exist, so everything is groovy. Unfortunately, I do define my <code>Functor</code> trait in a separate crate from my AST items, for pretty obvious separation-of-concern reasons, so everything I've said still stands. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-conflicts>↩</a></p></li><li id=fn-compiletimes><p>Except for compile times, of course :P It did eventually get so bad in my project that I had to make "being <code>Input</code>" opt-in, reducing hot-rebuild times from 30s to 5s. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-compiletimes>↩</a></p></li><li id=fn-dtolnay><p>I should really get off these... dtolnay did my boy JeanHeyd real dirty, introwospection in its full form would have been perfect for this project, but alas. If I can stomach runtime reflection instead of compile-time reflection (which might happen as soon as I take a real look at binary sizes), I should consider using <a href=https://docs.rs/facet-reflect/latest/facet_reflect/>facet-reflect</a> instead... <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-dtolnay>↩</a></p></li><li id=fn-unifying><p>Consider a struct declared as <code>A&lt;T></code> with field type <code>B&lt;T></code>, and a struct declared as <code>B&lt;U></code> with field type <code>C&lt;U></code>. When generating the edge <code>A -> C</code>, we want to turn the <code>C&lt;U></code> declaration into a <code>C&lt;T></code> one. You have to lookup the variables in the generic context to do the mapping appropriately, neat stuff! <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-unifying>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Reducing 404 Errors By Being Less Lazy]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-04-13-reducing-404-errors-by-being-less-lazy/" />
      <id>https://wolfgirl.dev/blog/2025-04-13-reducing-404-errors-by-being-less-lazy/</id>
      <published>2025-04-13T04:07:09Z</published>
      <updated>2025-04-13T04:07:09Z</updated>
      <summary><![CDATA[I had a problem[^1]: every time I posted something to this blog, I immediately got a bunch of 404 errors. To understand why, we must firs...]]></summary>
      <content type="html"><![CDATA[<p>I had a problem<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>: every time I posted something to this blog, I immediately got a bunch of 404 errors. To understand why, we must first understand how my crossposter worked:</p><ol><li>Create Mastodon post</li><li>Create Bluesky post</li><li>Create commit in GitHub with the URLs of the above posts, for comment URLs</li><li>GitHub Actions sees the commit and kicks off a build</li></ol><p>The problem with this is, the Mastodon and Bluesky posts were created <em>before</em> my blog post URL went live! Social media platforms like Mastodon and Bluesky like to include URL previews, and naturally they need to fetch the URL to do this. I could sort of tolerate just a few servers and fast-clicking users getting 404s, but uh, every fediverse server that syndicates with mine <em>also</em> fetches this URL very soon after posting, in an infamous "fedi hug of death".</p><p>The only possible fix is to make sure the post URL is live before creating the Mastodon/Bluesky posts. However, I also want to include the Mastodon/Bluesky post URLs in my final post, leading to a bit of a chicken-and-egg problem. I eventually decided it'd be fine to do 2 website rebuilds, one without the comment URLs and one with. Extending my existing publishing process, I thought I'd do this:</p><ol><li>Create commit in GitHub w/ no comment URLs</li><li>GitHub Actions sees this commit and kicks off a build.</li><li>At the end of the build, GitHub Actions hits a webhook on my server, letting it know which post it should continue the process for</li><li>Create Mastodon post</li><li>Create Bluesky post</li><li>Create a second commit in GitHub with the new comment links, with a special commit format telling GitHub not to hit my webhook a second time</li></ol><p>This is janky & nasty but it would probably work. I probably would have done it, too, <strong>if only GitHub Actions wasn't such a pain to work with</strong>. Very simple things like "parse some text out of a commit message" require shelling out to the classic <code>head | cut</code> and doing some nastiness w/ setting output variables, which, just why.</p><p>In my frustration, I had an epiphany: why am i using GitHub Actions at all?? I have a perfectly cromulent server running my crossposter already, so why not... just do the website build on there:</p><ol><li>Create commit in GitHub with no comment links</li><li>Build & publish website, using a simple shell script that my crossposter can wait for before moving onto the next step</li><li>Create Mastodon post</li><li>Create Bluesky post</li><li>Create a second GitHub commit</li><li>Build & publish website again</li></ol><p>lol<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>.</p><p>Comparing the <a href=https://github.com/p0lyw0lf/crossposter/blob/a7cc890fa0ee15b67faca6534a16ec6f3996866a/scripts/publish_blog.sh>new <code>publish_blog.sh</code></a><sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup> to the <a href=https://github.com/p0lyw0lf/website/blob/2424c13e617283b928635416fe4d3cb06b26c745/.github/workflows/main.yml>old GitHub Actions workflow</a>, it's actually insane how much incomprehensible boilerplate I've been able to get rid of. Much of it is related to saving/restoring caches, but a lot is also the YAML being very verbose. After replacing it with my own, much more comprehensible <a href=https://github.com/p0lyw0lf/crossposter/blob/a7cc890fa0ee15b67faca6534a16ec6f3996866a/poster/script.py>Python</a> & <a href=https://github.com/p0lyw0lf/crossposter/blob/a7cc890fa0ee15b67faca6534a16ec6f3996866a/shared/config/config.yaml#L23-L24>YAML</a> boilerplate, I understand my own system much better now. <a href=https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/>Write Your Own Tools</a> strikes again!!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I'm writing this post in past-tense because, with this post, I'm now using the new 404-less system! Was hoping it'd work first try, but it did not, and some fast clicking-users may have seen some side-effect posts as I was testing the system in prod oops <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>The GitHub commits are only so there's always a backup before doing any posting, there's probably a way to do this all in 1 commit, but whatever, I've done enough with this to be lazy again :P <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>Oh yeah I also switched to <a href=https://rclone.org/s3/#configuration><code>rclone sync</code></a> instead of <code>aws s3 sync</code>, because uploading my entire blog for every post turned out to be what was driving up my AWS costs, not <a href=https://wolfgirl.dev/blog/2025-03-15-i-just-need-to-optimize-my-aws-egress-patterns/>turning on logs</a>, lol. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Making My Own Art]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-04-07-making-my-own-art/" />
      <id>https://wolfgirl.dev/blog/2025-04-07-making-my-own-art/</id>
      <published>2025-04-07T15:41:48Z</published>
      <updated>2025-04-07T15:41:48Z</updated>
      <summary><![CDATA[So it's kinda cool taking photos of art compiled by many other people, sort of as a maximalist thing ([see also previously](https://wolfg...]]></summary>
      <content type="html"><![CDATA[<p>So it's kinda cool taking photos of art compiled by many other people, sort of as a maximalist thing (<a href=https://wolfgirl.dev/blog/2024-10-20-rit-pipe-graffiti-pictures/>see also previously</a>):</p><p><img alt="many flyers on a wall, some in chinese" src=https://static.wolfgirl.dev/polywolf/blog/01960294-717e-7447-90cd-4cbc2fecf44b/IMG_9322.jpeg></p><p>but also, it's fun to make my own art sometimes:</p><p><img alt='many many animal-themed memes drawn in yellow chalk on a blackboard. from left to right: Toriel holding a pie, "born to dilly-dally, forced to lock in" dog, "AWAKEN->PSYOP" cat wearing a wizard hat, "not on squeaking terms" rats, "it fucken WIMDY" fox, "laying in grass" wolf, "you might be running low on" red panda, S&P500 chart, E = mc^2 + AI, Crow Time crow, Dogdecahedron, Neco Arc, "low IQ meetup" orange cats, amogus, pleading fox, :(){:|:&};: cat, a kiwi bird eating a lemon and turning into a kiwi fruit, chick running between keyboard keys, a sleeping dog, a cat wearing food packaging, "damn" crow, Legoshi, gremlin cat, coat hanger cat, dog given a bath looking sad, "wawa" rainworld catslug, & a cat saying "JORP!"' title="click to view full resolution" src=https://static.wolfgirl.dev/polywolf/blog/01960294-717e-7447-90cd-4cbc2fecf44b/IMG_9326.jpeg></p><p>both this & my <a href=https://wolfgirl.dev/blog/2025-04-01-merry-april-fools/>recent doodle</a>, despite being pretty bad objectively, make me feel happy & communicated similar feelings i think. i should do more art.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Merry April Fools]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-04-01-merry-april-fools/" />
      <id>https://wolfgirl.dev/blog/2025-04-01-merry-april-fools/</id>
      <published>2025-04-02T01:15:26Z</published>
      <updated>2025-04-02T01:15:26Z</updated>
      <summary><![CDATA[here's a quick doodle i did on my phone in the spirit of FurAffinity's joke today  ![PolyWolf looking up at the viewer with a slight sm...]]></summary>
      <content type="html"><![CDATA[<p>here's a quick doodle i did on my phone in the spirit of FurAffinity's joke today</p><p><img alt="PolyWolf looking up at the viewer with a slight smile" title="despite it all, she is happy" src=https://static.wolfgirl.dev/polywolf/blog/0195f40f-36a5-7cc2-8567-3c5443715590/IMG_9301.png></p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I did my second V6 boulder today!!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-03-29-i-did-my-second-v6-boulder-today-/" />
      <id>https://wolfgirl.dev/blog/2025-03-29-i-did-my-second-v6-boulder-today-/</id>
      <published>2025-03-29T19:48:37Z</published>
      <updated>2025-03-29T19:48:37Z</updated>
      <summary><![CDATA[![](<https://static.wolfgirl.dev/polywolf/blog/0195e360-0944-7aa6-a7a1-43d27d9ecada/IMG_9285.jpeg>)  This red one. Something about slop...]]></summary>
      <content type="html"><![CDATA[<p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0195e360-0944-7aa6-a7a1-43d27d9ecada/IMG_9285.jpeg></p><p>This red one. Something about slopers where it pays to be flexible, & this corner, just clicks for me.</p><details><summary>Beta description if ur interested</summary> <ol><li>Post each hand on each starting hold, and put the right foot on the hold below the right starting hold</li><li>Right arm reaches to the left, grabs the hold to pull right, and use the post on the left arm to get the left foot on the hold below it's starting hold. This requires a little bit of flexibility.</li><li>Left hand matches the right, then the right goes up to the lower of the two holds between the start and the top four.</li><li>Left leg goes to starting hold, stand on it to make it straight, then right leg steps high to the lower of the holds on the right. This requires a fair amount of flexibility; I saw many others who could not do this like I did, but this felt good & worked for me.</li><li>Drop the left leg and really lean into the right leg & hand; physics dictates this position is quite stable. Left hand reaches up to the hold just above the right.</li><li>Right hand matches left, then left hand bumps once, twice. That second hold is good, and you can get your leg far over to the so-far-unused hold on the left.</li><li>Right matches left, left bumps, right matches again, now you're at the second to last hold. Left bumps a final time.</li><li>Right leg hooks the toe on the hold next to the left foot, right hand matches on final hold, it's stable enough to hang there, and you're done!</li></ol></details><p>It's been a while since <a href=https://wolfgirl.dev/blog/2024-11-02-i-did-my-first-v6-boulder-today-/>my last V6</a>. I blame the holiday season. Fun to get back into it tho, I am still improving, and this did not feel like a fluke. Have my eye on a V7 but seems like it requires a lot more grip strength than I have at the moment... That is what I like in a climb, lots of big slopers that challenge ur balance & positioning instead.</p><p>Fun gym, got a good group of friends there by now, here's to <a href=https://wolfgirl.dev/blog/2025-01-05-new-year-s-resolutions/>keeping my resolution</a>!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Quick Thoughts On: React vs SolidJS]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-03-20-quick-thoughts-on-react-vs-solidjs/" />
      <id>https://wolfgirl.dev/blog/2025-03-20-quick-thoughts-on-react-vs-solidjs/</id>
      <published>2025-03-20T16:56:15Z</published>
      <updated>2025-03-20T16:56:15Z</updated>
      <summary><![CDATA[TL;DR just use React, or Preact if you care about bundle size.  ---  A while ago, I [wrote my own post composer](https://wolfgirl.dev...]]></summary>
      <content type="html"><![CDATA[<p>TL;DR just use React, or Preact if you care about bundle size.</p><hr><p>A while ago, I <a href=https://wolfgirl.dev/blog/2024-11-08-what-if-anyone-could-use-my-post-composer-/>wrote my own post composer</a>, which I use to make blog posts from my phone, including this one. It's pretty nice!</p><p>One of the requirements I had when making it was "I want my bundle sizes to be as small as possible". Inspired by seeing my work's 66MB React app, I decided to look for other frontend frameworks, still reactive (because that <em>is</em> just the best paradigm), but smaller & faster.</p><p>Eventually, I settled on SolidJS. After having used it for a while on more than just a simple "Hello, World!" project, I can confidently claim there's a pretty fatal (for me) flaw:</p><h2><a aria-hidden=true class=anchor href=#solidjs-defaults-to-not-re-rendering-when-values-change id=heading-solidjs-defaults-to-not-re-rendering-when-values-change></a>SolidJS defaults to <em>not</em> re-rendering when values change</h2><p>From my understanding, this is how SolidJS works: everything is based around the "signal" as the underlying reactive primitive, with function calls as the way to subscribe to those signals:</p><pre class=syntax-highlighting><code class=language-jsx><span class=keyword>export</span> <span class=keyword>const</span> <span class=constructor>Component</span> <span class=operator>=</span> <span class=punctuation>(</span><span class=variable>props</span><span class=punctuation>)</span> <span class=operator>=></span> <span class=punctuation>{</span>
  <span class=keyword>const</span> <span class=punctuation>[</span><span class=variable>name</span><span class=punctuation>,</span> <span class=variable>setName</span><span class=punctuation>]</span> <span class=operator>=</span> <span class=function>createSignal</span><span class=punctuation>(</span><span class=string>""</span><span class=punctuation>);</span>
  <span class=keyword>const</span> <span class=function>title</span> <span class=operator>=</span> <span class=punctuation>()</span> <span class=operator>=></span> <span class=string>`</span><span class=punctuation>${</span><span class=variable>props</span><span class=punctuation>.</span><span class=property>greeting</span><span class=punctuation>}</span><span class=string>, </span><span class=punctuation>${</span><span class=function>name</span><span class=punctuation>()}</span><span class=string>`</span><span class=punctuation>;</span>
  <span class=keyword>return</span> <span class=punctuation>(</span>
    <span class=operator>&lt;</span><span class=variable>form</span><span class=operator>></span>
      <span class=operator>&lt;</span><span class=constructor>Title</span> <span class=property>text</span><span class=operator>=</span><span class=punctuation>{</span><span class=function>title</span><span class=punctuation>()}</span> />
      <span class=operator>&lt;</span><span class=variable>input</span> <span class=property>type</span><span class=operator>=</span><span class=string>"text"</span> <span class=property>value</span><span class=operator>=</span><span class=punctuation>{</span><span class=function>name</span><span class=punctuation>()}</span> <span class=property>onChange</span><span class=operator>=</span><span class=punctuation>{</span><span class=variable>e</span> <span class=operator>=></span> <span class=function>setName</span><span class=punctuation>(</span><span class=variable>e</span><span class=punctuation>.</span><span class=property>target</span><span class=punctuation>.</span><span class=property>value</span><span class=punctuation>)}</span> />
    &lt;/<span class=variable>form</span><span class=operator>></span>
  <span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><p>When <code>title()</code> is called, it will then call both <code>name()</code> (obviously) and <code>props.greeting</code> (non-obviously, since it's a special getter, which is why <a href=https://docs.solidjs.com/concepts/components/props#destructuring-props>you can't destructure <code>props</code></a>). By calling these functions, anything that calls <code>title()</code> will then know to re-render whenever either <code>name()</code> or <code>props.greeting</code> changes. So, <code>&lt;Title /></code> will re-render when <code>props.greeting</code> changes, and both <code>&lt;Title /></code> and <code>&lt;input></code> will re-render when <code>name()</code> changes.</p><p>However, if you <em>don't</em> make <code>title</code> a function, then <code>name()</code> and <code>props.greeting</code> will be called "too early", and <code>&lt;Title /></code> will never re-render; the body of <code>Component</code> isn't hooked up to any reactivity, just special places like the return value & <code>createEffect</code>.</p><p>This whole approach makes fine-grained reactivity quite simple, but also makes actually writing components a bit complex. You have to hook up all the reactive parts yourself!! It's very easy to get this wrong (I did many times), and the tooling (eslint) isn't helpful at detecting when it happens.</p><h2><a aria-hidden=true class=anchor href=#react-may-over-render-but-at-least-its-easier-to-conceptualize id=heading-react-may-over-render-but-at-least-its-easier-to-conceptualize></a>React may over-render, but at least it's easier to conceptualize</h2><p>Here's the equivalent React example; it's just minor syntactical differences:</p><pre class=syntax-highlighting><code class=language-jsx><span class=keyword>export</span> <span class=keyword>const</span> <span class=constructor>Component</span> <span class=operator>=</span> <span class=punctuation>({</span> greeting <span class=punctuation>})</span> <span class=operator>=></span> <span class=punctuation>{</span>
  <span class=keyword>const</span> <span class=punctuation>[</span><span class=variable>name</span><span class=punctuation>,</span> <span class=variable>setName</span><span class=punctuation>]</span> <span class=operator>=</span> <span class=function>useState</span><span class=punctuation>(</span><span class=string>""</span><span class=punctuation>);</span>
  <span class=keyword>const</span> <span class=variable>title</span> <span class=operator>=</span> <span class=string>`</span><span class=punctuation>${</span><span class=variable>greeting</span><span class=punctuation>}</span><span class=string>, </span><span class=punctuation>${</span><span class=variable>name</span><span class=punctuation>}</span><span class=string>`</span><span class=punctuation>;</span>
  <span class=keyword>return</span> <span class=punctuation>(</span>
    <span class=operator>&lt;</span><span class=variable>form</span><span class=operator>></span>
      <span class=operator>&lt;</span><span class=constructor>Title</span> <span class=property>text</span><span class=operator>=</span><span class=punctuation>{</span><span class=variable>title</span><span class=punctuation>}</span> />
      <span class=operator>&lt;</span><span class=variable>input</span> <span class=property>type</span><span class=operator>=</span><span class=string>"text"</span> <span class=property>value</span><span class=operator>=</span><span class=punctuation>{</span><span class=variable>name</span><span class=punctuation>}</span> <span class=property>onChange</span><span class=operator>=</span><span class=punctuation>{</span><span class=variable>e</span> <span class=operator>=></span> <span class=function>setName</span><span class=punctuation>(</span><span class=variable>e</span><span class=punctuation>.</span><span class=property>target</span><span class=punctuation>.</span><span class=property>value</span><span class=punctuation>)}</span> />
    &lt;/<span class=variable>form</span><span class=operator>></span>
  <span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><p>How React works is, any time the component props/<code>useState</code> values change, it runs the component body again, as well as any children, recursively. This gets us a lot of neat benefits over SolidJS, like:</p><ul><li>you can destructure <code>props</code></li><li>not everything has to be functions</li><li>if something changes in state, it is guaranteed to cause a re-render that will display the change.</li></ul><p>That approach would be expensive if, when a re-render happened, we had to remove all the old HTML elements from the page and add back the new ones, so instead React uses something called "Virtual DOM Diffing" to only call browser APIs for elements/attributes that need changing. Some unavoidable downsides are the fact you need to keep a virtual copy of the whole tree in-memory, as well as the pretty complex algorithm to do the diffing.</p><p>SolidJS cuts thru all this implementation complexity & potential runtime wastefulness by composing a simple + powerful primitive, signals. Unfortunately, it's a bit <em>too</em> simple + powerful, ending up finnicky to use as a result, like a Lisp, perhaps.</p><p>If you're making a non-trivial Javascript-powered application, just use React. The <a href=https://react.dev/learn/react-compiler>React Compiler</a> takes care of a lot of the problems React has, effectively providing fine-grained reactivity "for free" (bit more compile-time cost oop). <a href=https://preactjs.com/>Preact</a> is also available if you care about bundle sizes; the only reason I didn't choose it initially was because I wanted to learn a new framework. Still, sometimes u just gotta go back to ol' reliable.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I Just Need To Optimize My AWS Egress Patterns]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-03-15-i-just-need-to-optimize-my-aws-egress-patterns/" />
      <id>https://wolfgirl.dev/blog/2025-03-15-i-just-need-to-optimize-my-aws-egress-patterns/</id>
      <published>2025-03-15T14:41:07Z</published>
      <updated>2025-03-15T14:41:07Z</updated>
      <summary><![CDATA[To preface: I've been doing pretty well for myself at minimizing my cloud spend. I just have a static site in S3 behind CloudFront, so th...]]></summary>
      <content type="html"><![CDATA[<p>To preface: I've been doing pretty well for myself at minimizing my cloud spend. I just have a static site in S3 behind CloudFront, so the following table accounts for all of my spend<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>:</p><div class=table-wrapper><table><thead><tr><th align=left>Usage type</th><th align=left>Oct 2024</th><th align=left>Nov 2024</th><th align=left>Dec 2024</th><th align=left>Jan 2025</th><th align=left>Feb 2025</th></tr></thead><tbody><tr><td align=left>S3 Requests-Tier1</td><td align=left>$0.03</td><td align=left>$0.02</td><td align=left>$0.02</td><td align=left>$0.07</td><td align=left>$0.10</td></tr><tr><td align=left>S3 TimedStorage-ByteHrs</td><td align=left>$0.05</td><td align=left>$0.05</td><td align=left>$0.05</td><td align=left>$0.04</td><td align=left>$0.03</td></tr><tr><td align=left>S3 Requests-Tier2</td><td align=left>$0.02</td><td align=left>$0.02</td><td align=left>$0.02</td><td align=left>$0.02</td><td align=left>$0.02</td></tr><tr><td align=left><strong>Total costs</strong></td><td align=left><strong>$0.10</strong></td><td align=left><strong>$0.09</strong></td><td align=left><strong>$0.09</strong></td><td align=left><strong>$0.12</strong></td><td align=left><strong>$0.15</strong></td></tr></tbody></table></div><p>So clearly, I don't <em>need</em> to optimize my spend with it in the pennies n all, but there's still a worrying trend, and I like optimizing, so optimize I shall.</p><h2><a aria-hidden=true class=anchor href=#what-happened-in-january id=heading-what-happened-in-january></a>What Happened In January?</h2><p>Looking at CloudWatch metrics for my CloudFront distributions, we see that neither the number of requests nor the number of bytes downloaded lines up with the price increase I'm seeing<sup class=footnote-ref><a data-footnote-ref href=#fn-s3 id=fnref-s3>2</a></sup><sup class=footnote-ref><a data-footnote-ref href=#fn-bots id=fnref-bots>3</a></sup>:</p><figure><div class=table-wrapper><table><tbody><tr><td><p><img alt="Requests summed over 7 day periods. Green spikes in October, later November & early December, then tapers off in January and February. Blue is flat for most the rest, with Red and Orange at the very bottom." src=https://static.wolfgirl.dev/polywolf/blog/019599de-a4ef-7665-97df-7b1373ff6cfa/Screenshot%202025-03-15%20095227.png></p></td><td><p><img alt="Bytes downloaded summed over 7 day periods. Red & Orange spike in December and Feburary, with other smaller spikes still high above Green & Blue." src=https://static.wolfgirl.dev/polywolf/blog/019599de-a4ef-7665-97df-7b1373ff6cfa/Screenshot%202025-03-15%20095254.png></p></td></tr></tbody></table></div><figcaption>Green & Blue are the main sites, and Red & Orange are my static files</figcaption></figure><p>Instead, what I think is happening is that I started <a href=https://wolfgirl.dev/blog/2025-01-19-i-have-numbers-now/>analyzing my CloudFront access logs</a>. Whoops!</p><h2><a aria-hidden=true class=anchor href=#whats-wrong-with-the-access-log-egress-pattern id=heading-whats-wrong-with-the-access-log-egress-pattern></a>What's Wrong With The Access Log Egress Pattern?</h2><p>I have two hypotheses:</p><ol><li>You're not supposed to put the files from <a href=https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html>CloudFront access logs</a> into <code>{yyyy}/{MM}/{dd}</code> folders, because <code>LIST</code> requests are 10x as expensive as <code>GET</code> requests, or</li><li>There isn't a way to use the S3 access logs w/o incurring this cost, because CloudFront just loves to put a bunch of tiny files in ur bucket, and <code>GET</code>ing all those files will incur costs.</li></ol><p>Unfortunately, I can't really think of any way to solve this that doesn't involve services that are more expensive (Firehose, CloudWatch Logs)<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>4</a></sup> or fetching the files in some other way (AWS Lambda behind API Gateway behind CloudFront) which would still eat most of the same <code>LIST</code>/<code>GET</code> costs.</p><h2><a aria-hidden=true class=anchor href=#lets-do-some-math id=heading-lets-do-some-math></a>Let's Do Some Math</h2><p>I have about 15000 objects in my log buckets, across 60 days. I check it about once a day, so looking at the <a href=https://aws.amazon.com/s3/pricing/>S3 pricing page</a> & accounting for the slightly nested nature of some of the folders, we get:</p><pre class=syntax-highlighting><code>$ python
Python 3.11.11 (main, Dec  3 2024, 17:20:40) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 15000 * 0.0004 / 1000
0.006
>>> 30 * 65 * 0.005 / 1000
0.00975
</code></pre><p>hrm. That's not right. That should only show up as $0.02 on my bill, and clearly it's more than that, so I'm doing something wrong. Maybe it has to make a <code>HEAD</code> request to check if an object's been updated?</p><pre class=syntax-highlighting><code>>>> 30 * 15000 * 0.0004 / 1000
0.18
</code></pre><p>Now that's roughly the same order of magnitude, but now it's <em>too</em> large. Maybe I'm not actually checking my charts every day, but maybe I'm still doing my math wrong... Wait a minute, what about the <code>PUT</code> requests CloudFront makes to upload these objects in the first place?</p><pre class=syntax-highlighting><code>>>> 15000 * 0.005 / 1000
0.07
</code></pre><p>Bingo, we have a winner. Doesn't like up perfectly because I accidentally nuked part of it once, but yeah that'd do it, especially when combined with the uploads I do to my blog normally. Welp, nothing much I can do about that, mystery solved! So long as I <em>actually</em> run dashboard creation just once a day (or even more if I got that <code>LIST</code> calculation wrong), I think the cost will remain manageable (&lt;$0.20); I've paid a lot more for not understanding the Oracle Cloud "Always Free" tier lol.</p><p>Log ingestion is hard!! Still working at it, GoAccess is really nice but still needs a way to get the data. Same with whatever dashboard software I decide to use next<sup class=footnote-ref><a data-footnote-ref href=#fn-observable id=fnref-observable>5</a></sup>.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I host pretty much everything else on an Oracle Cloud Always Free VPS, which is quite beefy, at the expense of having to give Larry Ellison my credit card info & having the threat of the VPS being totally deleted for no reason at any point. Still 100% worth it for me, as you can see, with the rest of my spend in the pennies. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-s3><p>I know data out from CloudFront isn't a perfect analogue for S3 data charges, but it's what I have since turns out S3 doesn't enable access metrics by default. I've turned them on for my log buckets now, hopefully this doesn't start charging me something ridiculous... <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-s3>↩</a></p></li><li id=fn-bots><p>Every time I look at this chart I'm reminded I should probably put a <code>robots.txt</code> file in my static sites so scrapers don't download a bunch of files... <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-bots>↩</a></p></li><li id=fn-2><p>At least I'm pretty sure, looking at their pricing pages vs the S3 page and thinking about what my usage pattern looks like. Can't be 100% certain until I actually tried to build things with them, this is a classic AWS-ism :) <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-2>↩</a></p></li><li id=fn-observable><p><a href=https://observablehq.com/framework/>Observable Framework</a> looks pretty nice... i &lt;3 niche SSGs <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-observable>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[A Vague Sense There's Some Sort Of Dog At Your Feet]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-03-08-a-vague-sense-there-s-some-sort-of-dog-at-your-feet/" />
      <id>https://wolfgirl.dev/blog/2025-03-08-a-vague-sense-there-s-some-sort-of-dog-at-your-feet/</id>
      <published>2025-03-09T02:08:12Z</published>
      <updated>2025-03-09T02:08:12Z</updated>
      <summary><![CDATA[my dog is the cutest ever and these photos do a terrible job of explaining her cuteness so i'll do so between each picture <3  ![dog ly...]]></summary>
      <content type="html"><![CDATA[<p>my dog is the cutest ever and these photos do a terrible job of explaining her cuteness so i'll do so between each picture &lt;3</p><p><img alt="dog lying on the floor" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9192.jpeg></p><p>whenever i come home (to my parents' house), she greets me with a lot of trills & tail wagging</p><p><img alt="there is a hand petting the dog" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9193.jpeg></p><p>she's always carrying a toy in her mouth, different favorite every day, there's an entire box of toys to choose from and she does purposefully switch out sometimes</p><p><img alt="petting intensifies, causing blurry shapes" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9194.jpeg></p><p>our backyard is fenced-in, but she often finds a way to slip thru when we let her out at night to go pee to chase foxes. of course, it's only at night when we can't see her escape method</p><p><img alt="leaning against a pair of legs" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9196.jpeg></p><p>she always comes back eventually, though sometimes not before the neighbors find her & make a post on the neighborhood facebook group</p><p><img alt="petting with a foot" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9197.jpeg></p><p>out of my family, i am by far her favorite. one theory is that i was the first to hold her when we picked her up, but another is that i don't mind playing physical with her</p><p><img alt="back to hand petting" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9199.jpeg></p><p>she loves to wrestle, lick your face, jump up on your chair to see what's causing the tasty smell on the table</p><p><img alt="looking around while being pet" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9200.jpeg></p><p>she knows what the limits are, tho; she never bites or barks w/o good reason, and does not take food unless explicitly offered</p><p><img alt="curling back into the legs" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9201.jpeg></p><p>the house is dog-proofed, including the living room sofa she loves to jump up and snuggle on</p><p><img alt="pet the scruff" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9202.jpeg></p><p>her fur is so soft and i could just pet her forever. she loves being pet forever too, so this deal works out</p><p><img alt="looking around again" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9203.jpeg></p><p>honestly it's most forms of attention lol. she just loves to play and be loved</p><p><img alt="standing by the door, looking wistfully" src=https://static.wolfgirl.dev/polywolf/blog/01957892-761a-7bb6-b29e-775845a17176/IMG_9204.jpeg></p><p>she follows me around the house everywhere, and will start/end the day outside by bedroom, if not kenneled up</p><p>im gonna miss her when i leave :(</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I use the `<article>` semantic HTML tag like all the other cool kids now]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-03-05-i-use-the-article-semantic-html-tag-like-all-the-other-cool-kids-now/" />
      <id>https://wolfgirl.dev/blog/2025-03-05-i-use-the-article-semantic-html-tag-like-all-the-other-cool-kids-now/</id>
      <published>2025-03-06T04:06:05Z</published>
      <updated>2025-03-06T04:06:05Z</updated>
      <summary><![CDATA[FreshRSS has a feature where, if an RSS feed doesn't have full-text articles, it can fetch the page that's linked and extract the page's...]]></summary>
      <content type="html"><![CDATA[<p>FreshRSS has a feature where, if an RSS feed doesn't have full-text articles, it can fetch the page that's linked and extract the page's HTML content from a CSS selector.</p><p>So! I naturally put of doing this for all my feeds for a long time, just clicking thru to the source page, which is fine, that's how the author intended for the piece to be viewed right, doesn't even happen that often, I can totally ignore the repeated reminders of how I procrastinate doing small things that meaningfully improve my QoL<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>.</p><p>However! Tonight I was so bored I figured I might as well just configure all those FreshRSS CSS selectors, and power thru I did. The process led me to discover the vast majority of blogs have their main content surrounded in <code>&lt;article></code>. And here I was, using <code>&lt;div class="post"></code> like an absolute fool... This has been remedied. My procrastination of that Rust blog post I keep meaning to finish has not. Good night.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>A thin layer of dust that coats most of my room, a close-to-full laundry basket, 400+ undone WaniKani lessons... at least i cooked dinner tonight instead of doing a third "girl dinner" in a row ! <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: Who is Free Software For?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-03-04-rechost-who-is-free-software-for-/" />
      <id>https://wolfgirl.dev/blog/2025-03-04-rechost-who-is-free-software-for-/</id>
      <published>2025-03-04T19:40:37Z</published>
      <updated>2025-03-04T19:40:37Z</updated>
      <summary><![CDATA[<https://tante.cc/2025/03/03/who-is-free-software-for/>  Interesting read from [@tante](https://tldr.nettime.org/@tante). "For hackers...]]></summary>
      <content type="html"><![CDATA[<p><a href=https://tante.cc/2025/03/03/who-is-free-software-for/>https://tante.cc/2025/03/03/who-is-free-software-for/</a></p><p>Interesting read from <a href=https://tldr.nettime.org/@tante>@tante</a>. "For hackers by hackers" is pretty much exactly what I imagined when making <a href=https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/>Write Your Own Tools</a>, but he's exactly correct in that not everyone can or should do this.</p><p>Going to need to puzzle on this a bit more.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: Izzy's eXtension Modules]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-02-23-rechost-izzy-s-extension-modules/" />
      <id>https://wolfgirl.dev/blog/2025-02-23-rechost-izzy-s-extension-modules/</id>
      <published>2025-02-23T14:03:45Z</published>
      <updated>2025-02-23T14:03:45Z</updated>
      <summary><![CDATA[<https://ixm.one/>  "But PolyWolf!" I hear you say, "That's not a blog post, that's just someone's ([Izzy Muerte](https://izzys.casa/)'...]]></summary>
      <content type="html"><![CDATA[<p><a href=https://ixm.one/>https://ixm.one/</a></p><p>"But PolyWolf!" I hear you say, "That's not a blog post, that's just someone's (<a href=https://izzys.casa/>Izzy Muerte</a>'s) OSS project's landing page!"</p><p>And yes, you're somewhat correct. But contained in the <a href=https://ixm.one/guides/installation>guides</a> section is one of the, if not <em><strong>the</strong></em> most comprehensive series of posts on modern CMake that exist today. If you've worked with CMake recently, I highly recommend you check this out.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Some Albums I've Been Listening to A Lot Recently (#3)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-02-16-some-albums-i-ve-been-listening-to-a-lot-recently/" />
      <id>https://wolfgirl.dev/blog/2025-02-16-some-albums-i-ve-been-listening-to-a-lot-recently/</id>
      <published>2025-02-16T21:55:55Z</published>
      <updated>2025-02-16T21:55:55Z</updated>
      <summary><![CDATA[Before, when I was still using Spotify, I used their weekly mixes (Discovery, New Releases) and per-song "radio" as my primary method of...]]></summary>
      <content type="html"><![CDATA[<p>Before, when I was still using Spotify, I used their weekly mixes (Discovery, New Releases) and per-song "radio" as my primary method of discovering new music. However, despite buying my favorite artists' albums on Bandcamp & iTunes every year, I didn't feel great about contributing to Spotify's stranglehold/low payouts, so I switched to a different service<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>.</p><p>In doing so, I discovered why Spotify keeps its spot as the biggest game in town: it's not just the playlist non-portability, it's the fact that it's algorithmic recommendation system is leagues ahead of the competition<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>2</a></sup>.</p><p>So! I'm listen to albums now. Here are some of my favorites:</p><h2><a aria-hidden=true class=anchor href=#madeon---good-faith id=heading-madeon---good-faith></a><a href=https://music.apple.com/us/album/good-faith/1485487825>Madeon - Good Faith</a></h2><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0194fa4a-1a59-7885-9685-e5475c2cfa4b/IMG_9134.jpeg title=groovy></p><p>Man. What an album. My current favorites are "Hold Me Just Because" and "Heavy With Hoping", but yeah pretty much top-to-bottom bangers really. "Miracle" and "No Fear No More" were on an older playlist of mine, so it's also a bit nostalgic. Good album.</p><h2><a aria-hidden=true class=anchor href=#laufey---everything-i-know-about-love id=heading-laufey---everything-i-know-about-love></a><a href=https://music.apple.com/us/album/everything-i-know-about-love/1641539616>Laufey - Everything I Know About Love</a></h2><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0194fa4a-1a59-7885-9685-e5475c2cfa4b/IMG_9135.jpeg title=pretty></p><p>I heard "Fragile" as part of an <a href=https://youtube.com/shorts/X_eW1dNhz68>animated YouTube Short</a> and immediately had to listen to the full thing. As expected, I cried <em>hard</em>.</p><p>After that catharsis, the rest of the album didn't hit <em>quite</em> as much. But still enough to tug at the yearning lovesickness living inside most every 20-odd-year-old. Good album.</p><h2><a aria-hidden=true class=anchor href=#shibayanrecords---toho-bossa-nova-2 id=heading-shibayanrecords---toho-bossa-nova-2></a><a href=https://music.apple.com/ar/album/toho-bossa-nova-2/1490976828>ShibayanRecords - TOHO BOSSA NOVA 2</a></h2><p><img title="it's touhou" alt src=https://static.wolfgirl.dev/polywolf/blog/0194fa4a-1a59-7885-9685-e5475c2cfa4b/IMG_9136.jpeg></p><p>Like seeing an old friend after so long, this album welcomes me back in with open arms and a warm embrace. Yes it has the (at this point quite old) meme song on it but who cares it's my favorites TOHO BOSSA NOVA entry. Yes I've listened to all 13.</p><p>#14 is coming out in May of this year, like it always does, and I'll listen to that one too, but even if it usurps this, #2 will always have a special place in my heart. Good album.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Deezer, though I've also just switched to Apple Music<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>3</a></sup> because it pays artists even more per stream. Still a pitiful amount mind you, but better than nothing. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-3><p>I take this back: Apple Music seems to have roughly equivalent quality, at least for my niche tastes. At least it doesn't go into 2-track shuffle mode like Deezer does lol. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-3>↩</a></p></li><li id=fn-2><p>While cancelling Deezer, they showed a special promotion for 6 months at $9.99 instead of my current $11.99 rate 😉 <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Japan Trip Photos]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-02-15-japan-trip-photos/" />
      <id>https://wolfgirl.dev/blog/2025-02-15-japan-trip-photos/</id>
      <published>2025-02-16T03:53:09Z</published>
      <updated>2025-02-16T03:53:09Z</updated>
      <summary><![CDATA[I was on a 2-week long trip in Japan!! Apologies if I promised you regular photo updates; I told a lot of people I would do that, but ins...]]></summary>
      <content type="html"><![CDATA[<p>I was on a 2-week long trip in Japan!! Apologies if I promised you regular photo updates; I told a lot of people I would do that, but instead I decided it would be more convenient to wait 'til the end to collect my favorites & blast them out to everyone at once.</p><p>I do have a bit of a complex when it comes to sharing photos, however, so these may not be comprehensive, mostly just the highlights.</p><h2><a aria-hidden=true class=anchor href=#fukouka id=heading-fukouka></a>Fukouka</h2><p><img alt="A light breeze makes a wide, calm river slightly choppy, flanked on both sides by concrete embankments and apartment buildings. God rays peek out from" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8312.jpeg></p><p>I arrived quite early on a sleeper flight that I mostly slept in 1-hour bursts for. Getting outside and walking around helped a lot for adjusting to the new timezone.</p><p><img alt="A double-decker bike rack under a pedestrian overpass" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8329.jpeg></p><p>There were a lot of bikes here! This is just some cool bike parking. I also liked how small the cars were, with their boxy shapes to fit into small parking spaces and thin tires because they were light. International-style cars looked bulky by comparison. I wasn't really in any cities that <em>didn't</em> have a great subway system, but I hear it's like this in the rest of the country too.</p><h3><a aria-hidden=true class=anchor href=#other-things-in-fukouka id=heading-other-things-in-fukouka></a>Other Things In Fukouka</h3><ul><li>Best ramen of my life (so far)</li><li>Life-size Gundam in LaLaport (surprisingly hard to get a good picture of !)</li><li>7-11</li></ul><h2><a aria-hidden=true class=anchor href=#osaka id=heading-osaka></a>Osaka</h2><p><img alt="An out-of-service train is nestled between apartment buildings and overhead wires" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8353.jpeg></p><p>The train system really is excellent here. I did go on the Shinkansen (not pictured (they are a also bit tricky to photograph!)), and yeah it was just the best train I've ever ridden. The subway system is also probably the best, very comparable to London, tho the fact that they have all their signage/announcements in English (+ Chinese + Korean at times) tips Japan over the edge.</p><p><img alt="The inner courtyard of a small temple as viewed from the gate outside" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8385.jpeg></p><p>The big shrines/temples were very impressive indeed, but I also liked seeing all the smaller ones nestled all over the city. It is a very distinctive and cool-looking architecture genre.</p><h3><a aria-hidden=true class=anchor href=#other-things-in-osaka id=heading-other-things-in-osaka></a>Other Things In Osaka</h3><ul><li>Osaka castle</li><li>Tatio Station arcade</li><li>Mister Donut</li></ul><h2><a aria-hidden=true class=anchor href=#kyoto id=heading-kyoto></a>Kyoto</h2><p><img alt="Multiple black steam locomotives are lined up in housing bays in a large arc around a turntable" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8672.jpeg></p><p>The Kyoto Railway Museum was definitely worth a visit. Didn't have <em>everything</em> in English, but did have a lot, and plus it's just really cool to see a bunch of trains, no matter what language you speak. The museum also overlooks a pretty busy train intersection, fun to do trainspotting from there too.</p><p><img alt="A black-tailed kite (bird) soars above some trees" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8565.jpeg></p><p>Saw this cool bird on a hike!</p><p><img alt="A young deer looks expectantly past the camera" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8727.jpeg></p><p>Also saw the deer at Nara park, because of course :) They were so so so cool, very friendly with humans, and the park itself with all the temples nearby is also very good. I loved watching other tourists get swarmed by deer when they bought Shika Senbei :)</p><h3><a aria-hidden=true class=anchor href=#other-things-in-kyoto id=heading-other-things-in-kyoto></a>Other Things In Kyoto</h3><ul><li>Nijo Castle, with a bunch of cool sliding door art you can't take pictures of</li><li>Kyoto Tower</li><li>Meat vending machine<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup></li></ul><h2><a aria-hidden=true class=anchor href=#tokyo id=heading-tokyo></a>Tokyo</h2><p><img alt="A bustling downtown area builds up and around train lines" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8818.jpeg></p><p>Shibuya (southwest of Imperial Palace) was such a cool area (I participated in The Scramble!). I also really liked Harajuku (just north of Shinjuku) and Akihabara (northeast of Imperial Palace). All very distinct vibes, but all very energetic. So many small businesses mixing with large ones. And the number of malls!!</p><p><img alt="A Shiba Inu lies grumpily on the floor" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8865.jpeg></p><p>Went to a Shiba cafe in Asakusa, in the giant shopping complex near Senso-ji. Excellent time, they are trained to, if you put your jacket on your lap, sit down on it and curl up for you to pet them. They're so soft!!</p><p><img alt="A large red obelisk with golf-ball to basketball-sized dots glows ominously, just outside the verdana" src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8904.jpeg></p><p>Teamlab Planets Tokyo was amazing. Like, you read a normal art museum's marketing material about how it's a "full-body, immersive experience that challenges all the senses" and then the normal art museum will have a slightly larger painting. Whereas here, they are not kidding. Never before have I felt so transported, whisked away from this world into an all-encompasing, unfathomable alternate universe.</p><p>I promise you I'm not overhyping it. I only have this picture of the outside because the inside is just so hard to take pictures of, both because of the lighting and the fact that no picture (or even video!) can truly do it justice. It wasn't my #1 thing (that would be the arcades) but I am so glad I went and would highly recommend you go to.</p><h3><a aria-hidden=true class=anchor href=#other-things-in-tokyo id=heading-other-things-in-tokyo></a>Other Things In Tokyo</h3><ul><li>Meiji Jingu, with the large Torii and barrels of sake (Japanese) and wine (French)</li><li>Fushimi Inari-Taisha</li><li>Pokemon store</li><li>Kappabashidogugai street, where you can buy any kitchen supply you can imagine</li><li>So many arcades & crane games</li><li>Vtuber advertisements</li></ul><hr><p>And that's about it! So many stories, hope you liked what I've shared here. Japan in general was such a great place to be. Great food, great cities, great nature, affordable, just about everything one could want. Definitely want to go back someday. Not sold on living there just yet, maybe once I get better at Japanese tho...</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>No, I'm not kidding. I know you won't believe me, so here's proof: <img alt src=https://static.wolfgirl.dev/polywolf/blog/0194edb9-49e8-700e-91a8-505014531f61/IMG_8698.heic> <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: Resigning as Asahi Linux project lead]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-02-13-rechost-resigning-as-asahi-linux-project-lead/" />
      <id>https://wolfgirl.dev/blog/2025-02-13-rechost-resigning-as-asahi-linux-project-lead/</id>
      <published>2025-02-13T16:49:29Z</published>
      <updated>2025-02-13T16:49:29Z</updated>
      <summary><![CDATA[<https://marcan.st/2025/02/resigning-as-asahi-linux-project-lead/>  Sad day. I want to see Rust 4 Linux succeed, and losing a passionat...]]></summary>
      <content type="html"><![CDATA[<p><a href=https://marcan.st/2025/02/resigning-as-asahi-linux-project-lead/>https://marcan.st/2025/02/resigning-as-asahi-linux-project-lead/</a></p><p>Sad day. I want to see Rust 4 Linux succeed, and losing a passionate advocate who was willing to call out bad-faith opposition sucks. Can't say I wasn't expecting it tho; one can only deal with that situation + declining support for so long.</p><p>Hope he's able to recover from the burnout..</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Optimizing Markdown Images With Astro]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-02-12-optimizing-markdown-images-with-astro/" />
      <id>https://wolfgirl.dev/blog/2025-02-12-optimizing-markdown-images-with-astro/</id>
      <published>2025-02-13T03:36:35Z</published>
      <updated>2025-02-13T03:36:35Z</updated>
      <summary><![CDATA[I dug into this because it had been on my mind for a while, and I wanted to get something working before I uploaded Japan trip photos. A...]]></summary>
      <content type="html"><![CDATA[<p>A previous version of this post contained some factual errors. I'm re-publishing this under a new URL so that it will update in your RSS reader. Updates are noted where applicable.</p><hr><p>I dug into this because it had been on my mind for a while, and I wanted to get something working before I uploaded Japan trip photos. A couple nights later, I have learned a bit about how Astro & Remark/Rehype & Vite work, and a lot about how what I'm trying to do is futile.</p><p>From my understanding, this is how Astro optimizes <a href=https://docs.astro.build/en/guides/images/#images-in-markdown-files>local images in Markdown files</a>:</p><ol><li><a href=https://github.com/withastro/astro/blob/8d4e566f5420c8a5406e1e40e8bae1c1f87cbe37/packages/markdown/remark/src/remark-collect-images.ts>remark-collect-images.ts</a>, a Markdown AST transformation step, finds all the image paths that look like <code>![]()</code> and not <code>&lt;img src="" /></code>, so we only optimize the Markdown images. It records this information into context that's passed to the next step.</li><li><a href=https://github.com/withastro/astro/blob/8d4e566f5420c8a5406e1e40e8bae1c1f87cbe37/packages/markdown/remark/src/rehype-images.ts>rehype-images.ts</a>, an HTML AST transform step, turns all those images that look like <code>&lt;img src="./local.png" /></code><sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> and turns them into something that looks like <code>&lt;img __ASTRO_IMAGE_="{&amp;#x22;src&amp;#x22;:&amp;#x22;./local.png&amp;#x22;,&amp;#x22;index&amp;#x22;:0}" /></code>. It does this because it's easier to look for, and safer to parse later compared to raw HTML.</li><li><a href=https://github.com/withastro/astro/blob/8d4e566f5420c8a5406e1e40e8bae1c1f87cbe37/packages/astro/src/vite-plugin-markdown/images.ts>vite-plugin-markdown/images.ts</a> takes the list of local image URLs (generated as part of transforming the Markdown into HTML in the above steps), and the HTML with these <code>__ASTRO_IMAGE_</code> things in them, and generates a full-on Astro component, Javascript source & everything, to be rendered later.</li></ol><p>Phew, that's a lot!! Hope you're still following along :)</p><p>So, the procedure I described works perfectly fine for standalone <code>.md</code> pages. The problem comes when trying to apply this same transformation to markdown files rendered as part of a <a href=https://docs.astro.build/en/reference/modules/astro-content/>content collection</a>, which is what my blog files are stored as. Instead of being rendered as part of the "main" Astro flow, content collection entries are rendered separately, as part of a loader, like <a href=https://github.com/withastro/astro/blob/8d4e566f5420c8a5406e1e40e8bae1c1f87cbe37/packages/astro/src/content/loaders/glob.ts#L183>content/loaders/glob.ts</a>. For Markdown, this calls into <a href=https://github.com/withastro/astro/blob/8d4e566f5420c8a5406e1e40e8bae1c1f87cbe37/packages/astro/src/vite-plugin-markdown/content-entry-type.ts>vite-plugin-markdown/content-entry-type.ts</a>, which does no such image replacement, because content entries aren't in the correct stage of the build.</p><p><del>And I don't think we <em>can</em> get to the correct stage here! The whole reason this code does the <code>__ASTRO_IMAGE_</code> rigamarole is so it can call the image transformation function at the correct time. With content collections, all the rendering happens "too early", so the images can't be transformed.</del> UPDATE 2025-02-15: This is incorrect. The replacement happens in <a href=https://github.com/withastro/astro/blob/3842ce5ec9471d358042b3d9ef697cf06c7a91f6/packages/astro/src/content/runtime.ts#L401-L447>content/runtime.ts</a>, during the actual rendering, which makes sense. I wasn't seeing this before because (a) im bad at looking and (b) it's gated on Astro detecting images in the body, which it does not currently do for remote images.</p><p>Alternatively, I <em>could</em> use <a href=https://docs.astro.build/en/guides/integrations-guide/mdx/>MDX</a>, which is like Markdown but compiles to Javascript instead of HTML, which <em>does</em> always have the image transformation step, thanks to <a href=https://github.com/withastro/astro/blob/8d4e566f5420c8a5406e1e40e8bae1c1f87cbe37/packages/integrations/mdx/src/rehype-images-to-component.ts>rehype-image-to-component.ts</a>. However, I do not like MDX's weird character restrictions (c'mon, no <code>&lt;</code>, really??), so I will not use it >:(</p><p>At this point, I'm seriously considering moving away from Astro's built-in image optimization service to my own... For now though, I'm giving up 😔</p><p>UPDATE 2025-02-13: I have just discovered that <a href=https://docs.astro.build/en/guides/integrations-guide/markdoc/#custom-image-components>Markdoc</a> does exactly what I want it to: provide the ability to use a custom component to override what <code>![]()</code> means, without limiting Markdown syntax. This is very very good news! Here's hoping those files will still have reasonable build times. Also I should probably make it so my Post composer re-uses the Markdoc config...</p><p>UPDATE 2024-02-14: Markdoc also doesn't work for my needs because it doesn't have footnote support :((((( I make heavy use of footnotes in my text and I do not want to change how I write those. The whole <em>point</em> of Markdown is to make it so you don't look like you're formatting your text with tags, and Markdoc ignores that and immediately goes back to demanding you use tags for nonstandard features. It seems I am better served by the remark/rehype ecosystem for my parsing needs, so I will continue to do that.</p><p>UPDATE 2024-02-15: I did it! I fixed Astro!! The above information was a tad incorrect, what I wanted to do was possible with a bit of elbow grease, so I put in the work and opened a couple PRs: <a href=https://github.com/withastro/astro/pull/13254>#13254</a> & <a href=https://github.com/withastro/astro/pull/13256>#13256</a>. I also have the necessary patches for <a href=https://github.com/p0lyw0lf/website/blob/d7a866fef2356fb194527fc7cf48c8f20e616cc6/patches/astro.patch>astro</a> and <a href=https://github.com/p0lyw0lf/website/blob/d7a866fef2356fb194527fc7cf48c8f20e616cc6/patches/%40astrojs__markdown-remark.patch>@astrojs/markdown-remark</a> in my website directly, so I don't have to wait for them to be upstreamed.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I believe this is a minor bug: if you have both <code>![](./local.png)</code> <em>and</em> <code>&lt;img src="./local.png" /></code>, I think the latter image will be transformed when it shouldn't be. At least, that's if I'm reading the code right, which I may well not be :P <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I Have "Numbers" Now]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-01-19-i-have-numbers-now/" />
      <id>https://wolfgirl.dev/blog/2025-01-19-i-have-numbers-now/</id>
      <published>2025-01-19T20:59:14Z</published>
      <updated>2025-01-19T20:59:14Z</updated>
      <summary><![CDATA[My blog is hosted with CloudFront pointed at an S3 bucket, automatically updated to whenever I push a commit to the GitHub repository con...]]></summary>
      <content type="html"><![CDATA[<p>My blog is hosted with CloudFront pointed at an S3 bucket, automatically updated to whenever I push a commit to the GitHub repository containing the sources. Pretty simple in the end, but required a lot of reading AWS blog posts to get set up correctly initially. Now that it's no longer on Cohost, I have the opportunity to look at something I haven't been able to before: <strong><em>numbers</em></strong>. Unfortunately this required reading yet more AWS blog posts.!</p><h2><a aria-hidden=true class=anchor href=#where-do-numbers-come-from id=heading-where-do-numbers-come-from></a>Where Do Numbers Come From?</h2><p>From looking at an automatically-created CloudWatch (AWS's logging/metrics collection service) dashboard, I could see that CloudFront (AWS's CDN service) was already collecting... something...</p><p><img alt="CloudWatch dashboard showing total requests and total bytes downloaded from wolfgirl.dev and static.wolfgirl.dev" src=https://static.wolfgirl.dev/polywolf/blog/0194801b-532b-7224-8ae3-2fdeb7168007/Screenshot%202025-01-19%20151439.png></p><p>Great! So my website is getting hits! But on what pages? That was the question I wanted answered, and this dashboard would not or could not answer it.</p><p>Looking further, I found documentation for <a href=https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html>how to turn on CloudFront access logs</a>, which would contain the information I wanted in the <code>cs-uri-stem</code> field, among others. The data in CloudWatch currently was just "metrics", not "logs", though. If I wanted to put logs in CloudWatch, it would be... some unknown amount amount of money, based on usage. Given an example on the <a href=https://aws.amazon.com/cloudwatch/pricing/>CloudWatch pricing page</a>, I estimated about $1/mo.</p><p>Now that seems pretty reasonable, no? But currently, I pay just $0.10/mo for S3 storage, and nothing for anything else, so logs alone would 10x my costs :(. I am ruthless about making sure I pay as little as possible for the cloud services I use, so this was a non-starter.</p><p>Fortunately, there is another option: CloudFront can put its access logs directly into an S3 bucket, which I could analyze & rotate at my own convenience! Except... how?</p><h2><a aria-hidden=true class=anchor href=#where-do-numbers-go id=heading-where-do-numbers-go></a>Where Do Numbers Go?</h2><p>Initially, I thought "oh, I can just write a hacky Python script to download & parse them all". But then the thought of writing a hacky Python script seemed really unappealing to me, so I <a href=https://social.treehouse.systems/@PolyWolf/113851932867864276>lazyweb-ed on Mastodon</a> to see if anyone knew of any pre-built solutions to parse CloudFront logs. Fortunately <a href=https://woof.tech/@unlobito/113851963653376831>someone responded</a> (<a href=https://lobi.to/>unlobito</a>) suggesting a program called <a href=https://goaccess.io/>GoAccess</a><sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> that looked pretty good. Now, all I had to do was write some hacky Python that calls the following shell script!</p><pre class=syntax-highlighting><code class=language-bash><span class=comment>#!/usr/bin/env bash</span>

<span class=comment># Make it so if any individual step fails, the script exits early, among other things</span>
<span class=function>set</span> <span class=constant>-euo</span> pipefail
<span class=comment># Make it so ** acts as a recursive glob (not default on certain bash-es)</span>
<span class=function>shopt</span> <span class=constant>-s</span> globstar

<span class=keyword>if</span> [[ -z <span class=string>"</span><span class=embedded>${</span><span class=property>AWS_BUCKET_NAME</span><span class=embedded>+x}</span><span class=string>"</span> ]]; <span class=keyword>then</span>
  >&2 <span class=function>echo</span> <span class=string>"Must get bucket passed as AWS_BUCKET_NAME"</span>
  <span class=function>exit</span> 1
<span class=keyword>fi</span>

<span class=property>output_folder</span>=<span class=string>"/tmp/</span><span class=embedded>${</span><span class=property>AWS_BUCKET_NAME</span><span class=embedded>}</span><span class=string>"</span>
<span class=function>mkdir</span> <span class=constant>-p</span> <span class=operator>$</span><span class=property>output_folder</span>
<span class=property>output_file</span>=<span class=string>"/tmp/</span><span class=embedded>${</span><span class=property>AWS_BUCKET_NAME</span><span class=embedded>}</span><span class=string>.html"</span>
<span class=function>touch</span> <span class=operator>$</span><span class=property>output_file</span>

<span class=comment># Download all logs from S3. Must also receive environment variables that allow this to work</span>
<span class=function>aws</span> s3 sync <span class=constant>--delete</span> <span class=string>"s3://</span><span class=embedded>${</span><span class=property>AWS_BUCKET_NAME</span><span class=embedded>}</span><span class=string>"</span> <span class=string>"</span><span class=embedded>${</span><span class=property>output_folder</span><span class=embedded>}</span><span class=string>"</span> <span class=number>1</span>>&2

<span class=comment># Generate the report with GoAccess running inside Docker</span>
<span class=function>zcat</span> <span class=string>"</span><span class=embedded>${</span><span class=property>output_folder</span><span class=embedded>}</span><span class=string>/**/*.gz"</span> <span class=operator>|</span> <span class=function>docker</span> run <span class=constant>--rm</span> <span class=constant>-i</span> <span class=constant>-v</span> <span class=string>"</span><span class=embedded>${</span><span class=property>output_file</span><span class=embedded>}</span><span class=string>:/report.html"</span> <span class=constant>-e</span> LANG=<span class=operator>$</span><span class=property>LANG</span> allinurl/goaccess <span class=constant>-a</span> <span class=constant>-o</span> report.html <span class=constant>--log-format</span> CLOUDFRONT - <span class=number>1</span>>&2

<span class=function>echo</span> <span class=string>"</span><span class=embedded>${</span><span class=property>output_file</span><span class=embedded>}</span><span class=string>"</span></code></pre><p>GoAccess is pretty fast, and S3 doesn't take <em>that</em> long to download<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>, so I can get updated reports pretty easily. Now, drumroll please...</p><h2><a aria-hidden=true class=anchor href=#whats-my-most-viewed-page id=heading-whats-my-most-viewed-page></a>What's My Most Viewed Page?</h2><p><img alt="A table showing /blog/rss.xml, /, and /cybersec/rss.xml being the most-hit pages" src=https://static.wolfgirl.dev/polywolf/blog/0194801b-532b-7224-8ae3-2fdeb7168007/Screenshot%202025-01-19%20154654.png></p><p>It's RSS.</p><p>Guess I have a few readers huh! Hi 👋😸</p><h2><a aria-hidden=true class=anchor href=#other-neat-insights-i-didnt-expect id=heading-other-neat-insights-i-didnt-expect></a>Other Neat Insights I Didn't Expect</h2><ul><li>Firefox is outsize represented (50%) compared to it's overall market share (3%). Y'all are some nerds huh :3</li><li>The "bot problem" (automated vulnerability scanners, other crawlers) isn't <em>quite</em> a big a problem as I thought. RSS traffic is way higher.</li><li>The list of referring sites is pretty interesting; it's mostly feed syndicators, but seeing the range of ones I didn't know about is cool.</li></ul><p>I'll try not to abuse my newfound power <em>too</em> hard. Nice to have it available tho.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Somehow written in C, not Go?? lol <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>Hopefully, I haven't tested it on days/weeks worth of data, only hours. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[One Neat Trick For Writing Large `macro_rules!`]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-01-17-one-neat-trick-for-writing-large-macro-rules-/" />
      <id>https://wolfgirl.dev/blog/2025-01-17-one-neat-trick-for-writing-large-macro-rules-/</id>
      <published>2025-01-18T01:18:03Z</published>
      <updated>2025-01-18T01:18:03Z</updated>
      <summary><![CDATA[First, let's start with a related "neat trick" that lets you write _composable_ `macro_rules!`. Say you have a macro that generates struc...]]></summary>
      <content type="html"><![CDATA[<p>First, let's start with a related "neat trick" that lets you write <em>composable</em> <code>macro_rules!</code>. Say you have a macro that generates struct definitions<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>macro_rules</span>! node <span class=punctuation>{</span>
    <span class=punctuation>(</span>
        $name<span class=punctuation>:</span>ident <span class=punctuation>[</span> $subnode<span class=punctuation>:</span>ident <span class=punctuation>]</span>
    <span class=punctuation>)</span> => <span class=punctuation>{</span>
        <span class=keyword>pub</span> <span class=keyword>struct</span> $name<span class=punctuation>(</span><span class=constructor>Vec</span><span class=punctuation>&lt;</span>$subnode<span class=punctuation>>);</span>
    <span class=punctuation>};</span>
    <span class=punctuation>(</span>
        $name<span class=punctuation>:</span>ident <span class=punctuation>(</span> $<span class=punctuation>(</span>
            <span class=operator>*</span> $field<span class=punctuation>:</span>ident <span class=punctuation>:</span> $ty<span class=punctuation>:</span>ty
        <span class=punctuation>)</span>+ <span class=punctuation>)</span>
    <span class=punctuation>)</span> => <span class=punctuation>{</span>
        <span class=keyword>pub</span> <span class=keyword>struct</span> $name <span class=punctuation>{</span> $<span class=punctuation>(</span>
            $field<span class=punctuation>:</span> $ty<span class=punctuation>,</span>
        <span class=punctuation>)</span>+ <span class=punctuation>}</span>
    <span class=punctuation>};</span>
    <span class=punctuation>(</span>
        $name<span class=punctuation>:</span>ident <span class=punctuation>(</span> $<span class=punctuation>(</span>
            + $ty<span class=punctuation>:</span>ident
        <span class=punctuation>)</span>+ <span class=punctuation>)</span>
    <span class=punctuation>)</span> => <span class=punctuation>{</span>
        <span class=keyword>pub</span> <span class=keyword>enum</span> $name <span class=punctuation>{</span> $<span class=punctuation>(</span>
            $ty<span class=punctuation>(</span>$ty<span class=punctuation>),</span>
        <span class=punctuation>)</span>+ <span class=punctuation>}</span>
    <span class=punctuation>};</span>
<span class=punctuation>}</span></code></pre><p>Then, what you might want to do next is generate all these all in one macro invocation, so you don't have to write <code>node!()</code> over and over again. You might think, "darn, looks like I'll have to write all these exact branches again in order to capture them in an outer macro". But fear not, weary traveler! For I bear great news:</p><blockquote><p>Neat Trick 1: Use <code>tt</code> to capture arguments that you pass to other macro invocations.</p></blockquote><pre class=syntax-highlighting><code class=language-rust><span class=keyword>macro_rules</span>! nodes <span class=punctuation>{</span>
    <span class=punctuation>(</span> $<span class=punctuation>(</span>
        $name<span class=punctuation>:</span>ident $tt<span class=punctuation>:</span>tt <span class=punctuation>;</span>
    <span class=punctuation>)</span><span class=operator>*</span> <span class=punctuation>)</span> => <span class=punctuation>{</span>
        $<span class=punctuation>(</span>
            <span class=macro>node!</span><span class=punctuation>(</span>$name $tt<span class=punctuation>);</span>
        <span class=punctuation>)</span><span class=operator>*</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p><code>tt</code>, or TokenTree, represents a pair of braces (round <code>()</code>, square <code>[]</code>, or curly <code>{}</code>)<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>, with <em>anything</em> inside them. Rust is really good about enforcing braces to be in pairs even at the lex level, and this is represented in macros, which <em>do</em> just take in an arbitrary list of tokens.</p><p>So now we have a macro that we can use like this:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>mod</span> ast <span class=punctuation>{</span>
    <span class=macro>nodes!</span><span class=punctuation>(</span>
        <span class=constructor>Program</span><span class=punctuation>[</span><span class=constructor>Function</span><span class=punctuation>];</span>
        <span class=function>Function</span><span class=punctuation>(</span>
            <span class=operator>*</span>open_brace<span class=punctuation>:</span> <span class=constructor>OpenBrace</span>
            <span class=operator>*</span>body<span class=punctuation>:</span> <span class=constructor>Body</span>
            <span class=operator>*</span>close_brace<span class=punctuation>:</span> <span class=constructor>CloseBrace</span>
        <span class=punctuation>);</span>
        <span class=constructor>Body</span><span class=punctuation>[</span><span class=constructor>BlockItem</span><span class=punctuation>];</span>
        <span class=function>BlockItem</span><span class=punctuation>(</span> +<span class=constructor>Statement</span> +<span class=constructor>Declaration</span> <span class=punctuation>);</span>
        <span class=comment>// ...</span>
    <span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><p>But what if that's not good enough? What if we need <strong><em>more</em></strong>?</p><h2><a aria-hidden=true class=anchor href=#more id=heading-more></a>More?</h2><p>To keep things short: in my <a href=https://github.com/p0lyw0lf/pwcc>compiler project</a>, I have a proc_macro that wants to read in a whole bunch of <code>struct</code>/<code>enum</code> definitions at once, by being an attribute on a module. However! There is a dilemma: the simplest approach doesn't work.</p><pre class=syntax-highlighting><code class=language-rust><span class=attribute>#</span><span class=punctuation>[</span><span class=attribute>my_proc_macro</span><span class=punctuation>]</span>
<span class=keyword>mod</span> ast <span class=punctuation>{</span>
    <span class=macro>nodes!</span><span class=punctuation>(</span><span class=comment>/* ... */</span><span class=punctuation>);</span>
<span class=punctuation>}</span></code></pre><p>This is because of the order in which Rust runs macros. <code>#[my_proc_macro]</code> runs first, because it could transform the output to be <em>literally anything</em> (see the <code>#[cfg]</code> attribute macros, which wholesale remove the thing they're applied to). What is this means, however, is that when <code>#[my_proc_macro]</code> runs, all it sees is a module with a single macro invocation, which it doesn't like, so it moves on without doing any work :(. Only then does <code>nodes!()</code> run and generate all the <code>struct</code>/<code>enum</code> definitions, too late.</p><p>Our goal is to have <code>#[my_proc_macro]</code> operate on a module with expanded <code>struct</code>/<code>enum</code> definitions. Knowing how Rust evaluates macros, we have two options:</p><ol><li>Have <code>#[my_proc_macro]</code> eagerly expand all macro invocations inside it.</li><li>Have <code>nodes!()</code> generate everything in one shot, applying <code>#[my_proc_macro]</code> to the finished product.</li></ol><p>(1) is not possible unless we're The Rust Compiler/willing to resort to dirty hacks, so we're left with (2).</p><h2><a aria-hidden=true class=anchor href=#generating-everything-in-one-pass id=heading-generating-everything-in-one-pass></a>Generating Everything In One Pass</h2><blockquote><p>Neat Trick 2: Inside a repetition <code>$()*</code>, you can use <code>$()?</code> like individual <code>macro_rules!</code> cases, and the results will be the same, so long as you use an appropriate delimiter<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup>.</p></blockquote><pre class=syntax-highlighting><code class=language-rust><span class=keyword>macro_rules</span>! nodes_prime <span class=punctuation>{</span>
    <span class=punctuation>(</span>
        $<span class=punctuation>(</span>
            $name<span class=punctuation>:</span>ident
            $<span class=punctuation>(</span>
                <span class=comment>// Case 1</span>
                <span class=punctuation>[</span> $a_subnode<span class=punctuation>:</span>ident <span class=punctuation>]</span>
            <span class=punctuation>)</span>?
            $<span class=punctuation>(</span>
                <span class=comment>// Case 2</span>
                <span class=punctuation>(</span> $<span class=punctuation>(</span>
                    <span class=operator>*</span> $b_field<span class=punctuation>:</span>ident <span class=punctuation>:</span> $b_ty<span class=punctuation>:</span>ty
                <span class=punctuation>)</span>+ <span class=punctuation>)</span>
            <span class=punctuation>)</span>?
            $<span class=punctuation>(</span>
                <span class=comment>// Case 3</span>
                <span class=punctuation>(</span> $<span class=punctuation>(</span>
                    + $c_ty<span class=punctuation>:</span>ident
                <span class=punctuation>)</span>+ <span class=punctuation>)</span>
            <span class=punctuation>)</span>?
            <span class=comment>// Delimiter</span>
            <span class=punctuation>;</span>
        <span class=punctuation>)</span><span class=operator>*</span>
    <span class=punctuation>)</span> => <span class=punctuation>{</span>
        <span class=attribute>#</span><span class=punctuation>[</span><span class=attribute>my_proc_macro</span><span class=punctuation>]</span>
        <span class=keyword>mod</span> ast <span class=punctuation>{</span>
            <span class=keyword>use</span> <span class=keyword>super</span><span class=punctuation>::</span><span class=operator>*</span><span class=punctuation>;</span>

        $<span class=punctuation>(</span>
            $<span class=punctuation>(</span>
                <span class=comment>// Case 1</span>
                <span class=keyword>pub</span> <span class=keyword>struct</span> $name<span class=punctuation>(</span><span class=constructor>Vec</span><span class=punctuation>&lt;</span>$a_subnode<span class=punctuation>>);</span>
            <span class=punctuation>)</span>?
            $<span class=punctuation>(</span>
                <span class=comment>// Case 2</span>
                <span class=keyword>pub</span> <span class=keyword>struct</span> $name <span class=punctuation>{</span> $<span class=punctuation>(</span>
                    $b_field<span class=punctuation>:</span> $b_ty<span class=punctuation>,</span>
                <span class=punctuation>)</span>+ <span class=punctuation>}</span>
            <span class=punctuation>)</span>?
            $<span class=punctuation>(</span>
                <span class=comment>// Case 3</span>
                <span class=keyword>pub</span> <span class=keyword>enum</span> $name <span class=punctuation>{</span> $<span class=punctuation>(</span>
                    $c_ty<span class=punctuation>(</span>$c_ty<span class=punctuation>),</span>
                <span class=punctuation>)</span>+ <span class=punctuation>}</span>
            <span class=punctuation>)</span>?
        <span class=punctuation>)</span><span class=operator>*</span>

        <span class=punctuation>}</span>
    <span class=punctuation>};</span>
<span class=punctuation>}</span></code></pre><p>Whoa! That's a big macro, huh? Hopefully each part makes sense given the previous two macros.</p><p>How this trick works is, whenever Rust encounters a series of <code>$()?</code>, it checks the next token to see which branch matches. Assuming exactly one does, it takes that branch and emits that case within the repetition. Neat! However, there are some downsides:</p><ol><li>The criteria for making <code>$()?</code> cases distinct is stricter than making <code>macro_rules!</code> cases distinct, due to the eager one-token lookahead.</li><li>You must make the variable names in each case distinct, which either leads to visual clutter (prefixes) or confusion (no prefixes).</li><li>The compiler will yell at you real good if you mess up at either of the above.</li></ol><p>Still, at least for me, the benefit of generating everything in one shot & being compatible with <code>#[my_proc_macro]</code> easily outweighs the cost. I hope to have a blog post soon about what exactly I'm <em>doing</em> in <code>#[my_proc_macro]</code> that makes it so cool (whoops I hinted at such a post <a href=https://wolfgirl.dev/blog/2024-11-24-a-novel-idea-about-functor-in-rust/>months ago already</a>, oh well). Anyways, take care, see you next time!!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Apologies if you have trouble reading <code>macro_rules!</code> syntax from a cold start. I hope the usage example later helps. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>Yes, this list is complete, we're not forgetting angle <code>&lt;></code> braces, which need to be paired up in types. Rust doesn't force those to be paired up, because those tokens can also be part of comparison operators! <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>We can even nest this trick as much as we want! That's how we get things like <a href=https://github.com/p0lyw0lf/pwcc/blob/bf75533bba40f9f0a06ab66a7807a5f87c72147e/pwcc/src/parser/macros.rs#L24-L174>this monstrosity</a> :3 <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Belle]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-01-10-belle/" />
      <id>https://wolfgirl.dev/blog/2025-01-10-belle/</id>
      <published>2025-01-11T01:19:27Z</published>
      <updated>2025-01-11T01:19:27Z</updated>
      <summary><![CDATA[Pretty good. Visually stunning, and the music was excellent too. Don't think too hard about the plot or worldbuilding tho. Or even the ov...]]></summary>
      <content type="html"><![CDATA[<p>Pretty good. Visually stunning, and the music was excellent too. Don't think too hard about the plot or worldbuilding tho. Or even the overall themes besides "art is so pretty :)"</p><p>It at its best when it shows you a spectacle and lets you infer what the characters from body language/presentation alone, and does an excellent job of this many times.</p><details><summary>Spoilers</summary> But it's crazy to me that, after having such a good example (Suzu staring down the dad "protecting" the kids (watching him scamper away was cathartic)), they immediately have an "I love you very much", "Thanks you helped me get over my fear" dialogue. Like COME ON</details><p>It's Ghibli. You should watch it anyways no matter what I say.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Got My First Bowling Turkey!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-01-10-got-my-first-bowling-turkey-/" />
      <id>https://wolfgirl.dev/blog/2025-01-10-got-my-first-bowling-turkey-/</id>
      <published>2025-01-10T05:02:38Z</published>
      <updated>2025-01-10T05:02:38Z</updated>
      <summary><![CDATA[For those unaware of what that means: it is bowling slang for "three strikes in a row". Pretty slick!  I got it on the 10th frame of my...]]></summary>
      <content type="html"><![CDATA[<p>For those unaware of what that means: it is bowling slang for "three strikes in a row". Pretty slick!</p><p>I got it on the 10th frame of my first game, my touch was dialed in to hit them all back-to-back like that. However, I couldn't replicate another strike for the rest of the night. Had some fun impossible-looking curve shots for spares tho.</p><p>Bowling is fun while I am fresh but as the night wears on and I become tired (mentally & physically) it becomes more enjoyable to cheer others on than to maximize my own points.</p><p>Good stuff tho. Don't plan on doing it more often tho (:</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[New Year's Resolutions]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2025-01-05-new-year-s-resolutions/" />
      <id>https://wolfgirl.dev/blog/2025-01-05-new-year-s-resolutions/</id>
      <published>2025-01-05T16:55:17Z</published>
      <updated>2025-01-05T16:55:17Z</updated>
      <summary><![CDATA[* [ ] Do a V7 Boulder * [ ] Update My Wardrobe * [ ] Run a Half-Marathon (again)  ---  Wow, I haven't blogged since last year! :P...]]></summary>
      <content type="html"><![CDATA[<ul class=contains-task-list><li class=task-list-item><input checked class=task-list-item-checkbox disabled type=checkbox> 2025-07-22: <a href=https://wolfgirl.dev/blog/2025-07-22-i-did-my-first-v7-boulder-today-/>Do a V7 Boulder</a></li><li class=task-list-item><input class=task-list-item-checkbox disabled type=checkbox> Update My Wardrobe</li><li class=task-list-item><input class=task-list-item-checkbox disabled type=checkbox> Run a Half-Marathon (again)</li></ul><hr><p>Wow, I haven't blogged since last year! :P</p><p>Bit late on this one; Visited family for Christmas, friends of New Years, and going on a work trip tomorrow, so I've been keeping busy. Still feel like I should keep up this tradition, because they are good milestones and a time for me to remember Cohost (R.I.P. eggbug you will live on in our hearts). Below are some explanations of why I chose these goals for 2025:</p><h2><a aria-hidden=true class=anchor href=#bouldering id=heading-bouldering></a>Bouldering</h2><p>Quite recently, I did <a href=https://wolfgirl.dev/blog/2024-11-02-i-did-my-first-v6-boulder-today-/>my first V6 boulder</a>, and have felt pretty confident tackling other hard V5s and easy V6s, so I think a V7 next year is very doable. I climb with friends every week as my main form of irl socialization, so this resolution is less "make a new routine" and more "stick to a current routine and maybe improve on it". Not too much else to say about this one, I just love climbing!</p><h2><a aria-hidden=true class=anchor href=#clothing id=heading-clothing></a>Clothing</h2><p>Over the last year, as I've become more comfortable with myself being trans (finally out to core family!!), I've become painfully aware of how boymode-ed my closet is. I do not have a single dress, or skirt, or stocking, or pair of leggings, or even just a cool jacket. In total, I have:</p><ul><li>yoga pants (purchased during the exciting first months of transition)</li><li>sports bras (purchased later when breasts started to come in)</li><li></li><li>that's it that's the entire list</li></ul><p>Now don't get me wrong, I rock the t-shirt+jeans & athleisure fits quite well. But to have <em>nothing</em> feels a bit wrong. I want something better to wear, if not out in the world at large, then at least to the cool transgender parties. Target is 1-3 items.</p><h2><a aria-hidden=true class=anchor href=#running id=heading-running></a>Running</h2><p>I got COVID three times last year. That sucked big-time, because for 1/4 of the year I was doing nothing except bedrotting. It sucks even more because, after each round, I've been left with an increasingly worse lingering symptom: headaches. Fortunately, it's not a constant pain like it was more acutely to the infection, but basically any other headache I would otherwise get (stress, dehydration, etc.) is multiplied by 3. I am taken out if I don't carefully manage those.</p><p>However, after this last round, there's been an even worse outcome: I can't run anymore without getting a headache. Short jogs are fine, but as soon as I break 2 miles it gets pretty bad. Potentially a side-effect of a dehydration headache, and my slowness is potentially a result of not doing it for so long (and not because of anything more wrong, here's hoping!). Earlier, when I couldn't even go for walks without this happening, I was very very scared for my future. I count myself lucky that these symptoms are getting better over time at all.</p><p>So! This goal is actually 2 distinct goals hidden in a trench-coat: resolve the COVID symptoms preventing me from running, and then actually start running enough again to do a half-marathon. This is my most ambitious goal of the year, because I do not entirely control the first part of the process, but it's also my most desired one.</p><h2><a aria-hidden=true class=anchor href=#other-various-new-year-wishes id=heading-other-various-new-year-wishes></a>Other Various New Year Wishes</h2><ul><li>blog more <ul><li>more HN frontpages 😎</li></ul></li><li>go out more <ul><li>take more photos</li><li>make new friends</li><li>hang out with current friends more</li></ul></li><li>get better at Japanese <ul><li>level up a lot more in WaniKani (Japanese kanji learning app)</li><li>catch up with <em>Yotsuba&!</em> in Japanese</li></ul></li></ul><p>Happy New Years, hope y'all have a good one too!!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Cohost Export Dumpster Diving]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-12-15-cohost-export-dumpster-diving/" />
      <id>https://wolfgirl.dev/blog/2024-12-15-cohost-export-dumpster-diving/</id>
      <published>2024-12-16T04:00:13Z</published>
      <updated>2024-12-16T04:00:13Z</updated>
      <summary><![CDATA[At long last, I finally set aside some time to go thru my Cohost data export & salvage some posts for this blog! Got a few "ouuugh" momen...]]></summary>
      <content type="html"><![CDATA[<p>At long last, I finally set aside some time to go thru my Cohost data export & salvage some posts for this blog! Got a few "ouuugh" moments as I was browsing thru it again (list of all people who wished me happy birthday, by proxy of me using user notes for that exclusively). Also, belated apologies to the RSS users who saw all 27 posts appear at once :P</p><p>I <em>would've</em> used the <a href=https://wolfgirl.dev/tags/cohost/>#cohost</a> tag to collect all the posts, but uh, there are quite a lot, so I've opted to not do that. Instead, my organization scheme is roughly as follows:</p><ul><li>All programming things go in <a href=https://wolfgirl.dev/tags/programming/>#programming</a> (obviously)</li><li>All media reviews go in <a href=https://wolfgirl.dev/tags/reviews/>#reviews</a></li><li>All writing goes in <a href=https://wolfgirl.dev/tags/writing/>#writing</a></li><li>All personal updates go in <a href=https://wolfgirl.dev/tags/wolfgirlagenda/>#wolfgirlagenda</a></li><li>A smattering of all of the above goes in <a href=https://wolfgirl.dev/tags/cohost/>#cohost</a> if I think it's particularly emblematic of my time on the site, or if it's related to the site after its demise (2024-10-01, NEVER FORGET)</li></ul><p>There's also this post, which I'll use to collect my absolute favorite chosts:</p><h2><a aria-hidden=true class=anchor href=#5-choose-your-own-adventure id=heading-5-choose-your-own-adventure></a>5: <a href=https://wolfgirl.dev/blog/7283112/>Choose Your Own Adventure</a></h2><p>A spontaneous game I played where I built a scene out of very concise audience provided responses. I got kinda burned out of it tho, which shows, but I still think there's some cool stuff in there.</p><h2><a aria-hidden=true class=anchor href=#4-software-packaging-is-a-nightmare id=heading-4-software-packaging-is-a-nightmare></a>4: <a href=https://wolfgirl.dev/blog/2613009/>Software Packaging Is A Nightmare</a></h2><p>Fairly popular at the time, and I did spend a lot of time on it, but I am not so sure how it holds up. Judge for yourself!</p><h2><a aria-hidden=true class=anchor href=#3-the-one-where-i-rationalize-why-im-not-trans-and-the-one-where-i-come-out-as-trans id=heading-3-the-one-where-i-rationalize-why-im-not-trans-and-the-one-where-i-come-out-as-trans></a>3: <a href=https://wolfgirl.dev/blog/679878/>The One Where I Rationalize Why I'm Not Trans</a> and <a href=https://wolfgirl.dev/blog/759538/>The One Where I Come Out As Trans</a></h2><p>Ah, good 'ol transitory life periods, where I have lots of time to reflect on who I am and who I want to be, and enough exposure to trans people to be able to realize some things finally.</p><p>I've realized even more since writing these pieces. Nice to look back on them as time-capsules of what I was thinking in that moment in time.</p><h2><a aria-hidden=true class=anchor href=#2-exploring-mutexes-in-c-and-learning-memory-order-in-c-with-mutexes id=heading-2-exploring-mutexes-in-c-and-learning-memory-order-in-c-with-mutexes></a>2: <a href=https://wolfgirl.dev/blog/2064554/>Exploring Mutexes in C</a> and <a href=https://wolfgirl.dev/blog/2091838/>Learning Memory Order in C with Mutexes</a></h2><p>The mutex posts!! I am probably inordinately proud of these, but I do think they are and will continue to be useful introductory pieces for this subject matter.</p><h2><a aria-hidden=true class=anchor href=#1-polywolfs-style-guide id=heading-1-polywolfs-style-guide></a>1: <a href=https://wolfgirl.dev/blog/6372078/>PolyWolf's Style Guide</a></h2><p>In which I lay out some grammatical, punctuational, & other rules to follow when drafting my blog posts. Perhaps a bit unorthodox as my top choice, especially since it wasn't nearly as well-received at the time as all the other pieces on this list, but I think it holds up very well as a piece of humor, plus I... actually follow these rules now?? It's the most formative of all, in that sense.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[The Agile Philosophy Is Taking A Toll On My Mental]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-12-13-the-agile-philosophy-is-taking-a-toll-on-my-mental/" />
      <id>https://wolfgirl.dev/blog/2024-12-13-the-agile-philosophy-is-taking-a-toll-on-my-mental/</id>
      <published>2024-12-13T14:37:55Z</published>
      <updated>2024-12-13T14:37:55Z</updated>
      <summary><![CDATA[So we don't really "do Agile" at my job. There are no well-defined sprints, no retrospectives, no story points, no Kanban, no burndown ch...]]></summary>
      <content type="html"><![CDATA[<p>So we don't really "do Agile" at my job. There are no well-defined sprints, no retrospectives, no story points, no Kanban, no burndown chart, no project managers even. Just a bunch of programmers in loosely-organized teams, coordinating with a designer.</p><p>By many accounts, we're doing Agile right! Bare minimum processes that work! Individuals interacting together & with customers to deliver what people actually want! The perversion that is <a href=https://ludic.mataroa.blog/blog/tossed-salads-and-scrumbled-eggs/>Scrum</a> has no place here.</p><p>So why am I <em>still so angry?</em></p><h2><a aria-hidden=true class=anchor href=#the-never-ending-treadmill id=heading-the-never-ending-treadmill></a>The Never-Ending Treadmill</h2><p>Let's say you're launching a new feature. Let's say the backend stuff has mostly been solved at this point in time. Let's say you're one of the two frontend engineers in the company who can manipulate Flexbox without resorting to an LLM, and the other is busy with a different project.</p><p>In such a scenario, you'd be primarily responsible for making sure the frontend is spick-n-span. Ideally, the designer would present you with a design, and enumerate the ways in which the code differs from the design, and you could rectify this within a set amount of time, and all would be well.</p><p>In actuality, your company is dogfooding the beta version of the feature in order to uncover any pain-points, re-designing parts on-the-fly in a poorly-organized Figma where some screens still reflect the old design. And the designer isn't quite happy with how a standard component looks, so now you have to change it for the entire app & make sure that doesn't introduce any new bugs. And oh by the way there's new bugs in all these areas you touched previously.</p><p>The pile of work just keeps growing and growing, faster than you can keep up. The attention to detail you're so proud of slows you down & makes you invaluable at the same time. The designer keeps adding to your list every time you interact with them so you start to dread those interactions. You have no idea what to prioritize since everything keeps coming in so fast so you just work on whatever the CEO seems most interested in at the time & let everything else fester until it becomes too big a problem to ignore.</p><p>Why yes, I <em>am</em> speaking from experience! Not just recently (tho that's what's pushing me over the edge), but <em>every single project at this company</em> has worked this way.</p><h2><a aria-hidden=true class=anchor href=#why-am-i-so-mad id=heading-why-am-i-so-mad></a>Why Am I So Mad?</h2><p>I'm mad because it feels like I'm not respected, treated like an errand dog, constantly tasked instead of having real agency in what I work on. I'm mad because it feels like all tasks are considered equal, across all 4 quadrants of {easy, hard} x {important, unimportant}, and no-one listens when I think something is hard & unimportant. I'm mad because I see my coworkers in the same predicament, burning the candle at both ends to ship new features, and for what? Delivering "working" code just a little bit earlier, instead of being able to take the time to deliver absolutely bug-free software, no matter the time cost.</p><p>I'm mad because tests and documentation are nonexistent. I'm mad that the new engineer we just onboarded had to go thru that grueling learning curve process even tho I swore to change things after going thru it myself. I'm mad that I'm the only one who seems to give a darn about these kinds of things. I'm mad I don't have the time to fix them. I'm mad that the startup culture "grow or die" mentality/reality makes things this way.</p><p>Here's the kicker: I'm mad because the designer asked me to put a "remove all" button after a series of "remove" buttons.</p><p>It sounds silly writing it down now; it's actually a very reasonable request! But it really was a straw-that-broke-the-camel's-back situation. Compounding stresses from a misalignment of the things I care about deeply and the tenets of Agile/startup culture.</p><h2><a aria-hidden=true class=anchor href=#what-will-i-do-about-it id=heading-what-will-i-do-about-it></a>What Will I Do About It?</h2><p>First, I'll channel this anger into writing this piece instead of saying something I'll regret in workplace-monitored chat. And I'll also "take a breather" (procrastinate out of spite) for a bit.</p><p>Then, I'm going to temporarily ignore the designer. I'm going to work on the things I think are important to regain a sense of agency. I'm going to talk to coworkers more, to see them as people instead of task-giving machines. I'm going to dust off my résumé just in case things get worse.</p><p>But most importantly, I'll do my best to rediscover the joy in what I do (programming <em>is</em> fun after all!), despite everything above. Can't stay mad forever.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Minor Blog Status Updates]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-12-07-minor-blog-status-updates/" />
      <id>https://wolfgirl.dev/blog/2024-12-07-minor-blog-status-updates/</id>
      <published>2024-12-08T04:19:11Z</published>
      <updated>2024-12-08T04:19:11Z</updated>
      <summary><![CDATA[I've updated the CSS in my post composer to be a bit more like the CSS in my actual blog; we have better link, code, & blockquote styles,...]]></summary>
      <content type="html"><![CDATA[<p>I've updated the CSS in my post composer to be a bit more like the CSS in my actual blog; we have better link, code, & blockquote styles, to be specific. It's not exact since I don't really want to pull in an entire syntax highlighting library & I'm too lazy to actually put everything in a monorepo where it can share CSS (tho, those are both things I might want to do eventually.</p><p>I've also updated my crossposter to post tags on Mastodon and Bluesky whenever I post something here. However, uh, i haven't added any tags to this post, so I won't know if it works until I write a future post, whoops (:</p><p>Last thing: apparently, my <a href=https://wolfgirl.dev/blog/2024-11-24-a-novel-idea-about-functor-in-rust/>`Functor` in Rust</a> post made it onto HackerNews?? I think the full path was bsky -> lobste.rs -> HN, which checks out. And as it turns out, getting onto HN is pretty good for SEO: my click rate saw a tenfold increase<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>. However, I made the classic mistake of looking at the HN comments, I really should not have done that...</p><p>In any case! I don't plan on "optimizing" my content for <em>anything</em>. I am very proud of that blog post, not because of some external validation<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>, but because I am pleased with my writing quality for it. I will continue to post the things that I want, and right now, I felt like giving a small update. good night :)</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Which is to say, my clicks per month jumped from 2 to 20 :P <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>From orange site/other randos, more so. I do really appreciate external validation from friends, from "liking the crossposted announcement post" to "writing me an email", lets me know people care <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[It's Bandcamp Friday & I Bought My Favorite Album Of 2024 (#2)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-12-06-it-s-bandcamp-friday-i-bought-my-favorite-album-of-2024/" />
      <id>https://wolfgirl.dev/blog/2024-12-06-it-s-bandcamp-friday-i-bought-my-favorite-album-of-2024/</id>
      <published>2024-12-07T02:58:35Z</published>
      <updated>2024-12-07T02:58:35Z</updated>
      <summary><![CDATA[So as to not bury the lede:  ## 1: [Tanger - Prefer Not To Say](https://tangermusic.bandcamp.com/album/prefer-not-to-say)  It's reall...]]></summary>
      <content type="html"><![CDATA[<p>So as to not bury the lede:</p><h2><a aria-hidden=true class=anchor href=#1-tanger---prefer-not-to-say id=heading-1-tanger---prefer-not-to-say></a>1: <a href=https://tangermusic.bandcamp.com/album/prefer-not-to-say>Tanger - Prefer Not To Say</a></h2><p>It's really really good. I love how all the songs fade into one another. I love the variety & how it feels cohesive despite that. I love the beat, lyrics, album art, just everything. Phenomenal pieces of electronic music. And they're only 19??</p><h2><a aria-hidden=true class=anchor href=#2-potsu---star-crossed id=heading-2-potsu---star-crossed></a>2: <a href=https://potsu.bandcamp.com/album/star-crossed>Potsu - STAR-CROSSED</a></h2><p>I purchased this one last Bandcamp Friday, purely on the basis of "if it's potsu, it's gotta be good", and sure enough yep, it's really good too. Some classic potsu stuff here, but also some new stuff which works quite well. Really hard to say if it's better than <a href=https://potsu.bandcamp.com/album/ivy-league>ivy league</a> or not, tho...</p><hr><p>Besides the albums mentioned in my <a href=https://wolfgirl.dev/blog/2024-10-04-bandcamp-friday-haul/>last Bandcamp Friday post</a>, I don't really have any other contenders for "album i liked the most this year that was released this year". However, I do have some other albums from this Friday's haul I'd like to talk about, so talk about them I shall:</p><h2><a aria-hidden=true class=anchor href=#greyl---made-for-you id=heading-greyl---made-for-you></a><a href=https://greyl.bandcamp.com/album/made-for-you-2>greyl - made for you</a></h2><p>greyl is another artist who, whenever they release an album, I know I will like it. Just a master of doing funky anime mashups. Their <a href=https://greyl.bandcamp.com/album/eisei-ep>EISEI EP</a> and <a href=https://greyl.bandcamp.com/album/02-02>02:02</a> projects, released under the name EISEI, are still my favorites, but i do always appreciate more :)</p><h2><a aria-hidden=true class=anchor href=#scranton---love-letter-to-the-light id=heading-scranton---love-letter-to-the-light></a><a href=https://scraton.bandcamp.com/album/love-letter-to-the-light>SCRANTON - Love Letter To The Light</a></h2><p>So what if they're a brony? They make some pretty bangin' dubstep. I am only vaguely familiar with their work, remember liking a few specific songs in the past, nothing off the top of my head tho. This self-proclaimed return-to-form hits very good for me.</p><h2><a aria-hidden=true class=anchor href=#snails-house---snö id=heading-snails-house---snö></a><a href=https://0101.bandcamp.com/album/sn>Snail's House - Snö</a></h2><p>Bit old at this point, but it was just sitting in my wishlist & I've kept on forgetting to buy it whoops. Kinda nostalgic for me tbh, takes me back to when I first discovered Snail's House... the <a href="www.youtube.com/watch?v=QDCe1_SzHAc">MV</a> for their most recent single, <a href=https://0101.bandcamp.com/album/--4>う​た​か​た (utakata)</a> had me in tears, really quite moving. Anyways, this album is definitely meant to be listened to as an album, which I appreciate :)</p><hr><p>Deezer's music recommendation algorithm sucks, so I've started listening to more albums in general now. It's pretty good! Turns out musicians bundle songs into an album for a reason, or something. The only downside is I am running out of albums; I do not like to listen to the same thing too many times, 'tis why I tend to make monster playlists that I can just put on shuffle & not think about. Maybe I should make my next one of those soon... Always accepting recs!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[A Novel Idea About `Functor` In Rust?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-24-a-novel-idea-about-functor-in-rust/" />
      <id>https://wolfgirl.dev/blog/2024-11-24-a-novel-idea-about-functor-in-rust/</id>
      <published>2024-11-24T22:24:16Z</published>
      <updated>2024-11-24T22:24:16Z</updated>
      <summary><![CDATA[I may have accidentally come up with a paper-worthy idea while just playing around with my silly compiler project]]></summary>
      <content type="html"><![CDATA[<p>So. In <a href=https://wolfgirl.dev/blog/2024-11-13-swift-almost-does-a-really-cool-thing-i-want/>my last post</a> about this compiler project, I wrote out the following trait definition, explaining that it's what I was using to do type-safe AST transformations:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Inner</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Output</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>;</span>
<span class=punctuation>}</span></code></pre><p>I called it a "specialized Functor" because I didn't quite understand what exactly a Functor was, but I knew that, at the very least, what I had was a little different.</p><p>Upon reading this, Alice of <a href=https://welltypedwit.ch/>welltypedwit.ch</a>, who actually knows what she's talking about when it comes to functional programming, <a href=https://bsky.app/profile/welltypedwit.ch/post/3latirgqtjk2h>pointed me towards</a> a Haskell package called <a href=https://hackage.haskell.org/package/uniplate-1.6.13>uniplate</a>, noting that what I was doing sounded like its <a href=https://hackage.haskell.org/package/uniplate-1.6.13/docs/Data-Generics-Uniplate-Operations.html#v:transformBi><code>transformBi</code></a> operation.</p><p>To fully understand what she meant by this, let's break down the type signature:</p><h2><a aria-hidden=true class=anchor href=#transformbi id=heading-transformbi></a><code>transformBi</code></h2><p>In Haskell:</p><pre class=syntax-highlighting><code class=language-haskell>transformBi :: Biplate from to => (to -> to) -> from -> from
</code></pre><p>What is this saying? Even if you understand Haskell, it can sometimes still be tricky to get what certain operations <em>mean</em>. In this case, I think about it like:</p><blockquote><p>Given a type <code>From</code>, which is a container type like the root of an AST, and a type <code>To</code>, which is like an inner node of an AST, transform <code>From</code> by applying an operation each time we encounter a node of type <code>To</code> in the tree.</p></blockquote><p>Indeed, that is very similar to what I was trying to do previously! So why did I call my trait <code>Functor</code>, when this description almost exactly matches? Well mostly out of ignorance, yes, but also, when I later looked up the equivalent Rust crate <a href=https://docs.rs/uniplate/latest/uniplate/trait.Biplate.html><code>uniplate</code></a>, its type signature looked something more like<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>Biplate</span><span class=punctuation>&lt;</span><span class=type>To</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>fn</span> <span class=function>transform_bi</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>op</span><span class=punctuation>:</span> <span class=keyword>fn</span><span class=punctuation>(</span><span class=type>To</span><span class=punctuation>)</span> -> <span class=type>To</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>;</span>
<span class=punctuation>}</span></code></pre><p>Quite different from what I ended up with! Note that this is <em>homogenous</em> in the types it maps over; it transforms ASTs to be ones of the same type, whereas I was interested in transforming ASTs to have <em>different types</em>, because the type of the AST can enforce specific invariants. So that's why I was, and still am, interested in something like <code>fmap</code> instead:</p><h2><a aria-hidden=true class=anchor href=#fmap id=heading-fmap></a><code>fmap</code></h2><p>In <a href=https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Functor.html>Haskell</a>:</p><pre class=syntax-highlighting><code class=language-haskell>fmap :: Functor f => (a -> b) -> f a -> f b
</code></pre><p>I think about this like:</p><blockquote><p>Given a container type <code>F</code> over homogenous elements <code>A</code>, you can transform each <code>A</code> into a <code>B</code> in order to get a new container <code>F</code> over homogenous elements <code>B</code>.</p></blockquote><p>And <em>that's</em> why I thought I wanted a <code>Functor</code>! Initially, I had only parameterized my AST on how it represented locations in memory. I had one type (<code>A</code>) for <code>HardwareRegister | StackAddress | PseudoRegister</code>, and another (<code>B</code>) for just <code>HardwareRegister | StackAddress</code>, in order to guarantee I could never emit assembly code that contained pseudo-registers. A regalloc<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup> pass would get rid of all the pseudo-registers, and I would run that pass with a specially-crafted <code>fmap</code> call.</p><p>I was inspired by how the Rust crate <a href=https://docs.rs/fmap/latest/fmap/trait.Functor.html><code>fmap</code></a> translated the Haskell signature<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup>:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>B</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Inner</span><span class=punctuation>;</span>  <span class=comment>// a.k.a. A</span>
    <span class=keyword>type</span> <span class=type>Mapped</span><span class=punctuation>;</span> <span class=comment>// a.k.a. F&lt;B></span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>op</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Inner</span><span class=punctuation>)</span> -> <span class=type>B</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>;</span>
<span class=punctuation>}</span></code></pre><p>Now you can hopefully understand how I ended up where I did! My version of <code>Functor</code> is exactly this, just with an explict associated parameter for <code>B</code> so writing generic implementations is easier<sup class=footnote-ref><a data-footnote-ref href=#fn-6 id=fnref-6>4</a></sup>.</p><h2><a aria-hidden=true class=anchor href=#going-beyond id=heading-going-beyond></a>Going beyond?</h2><p>While playing around with this <code>Functor</code> trait, I realized something interesting. Unlike Haskell's <code>Functor</code> typeclass, where <code>f</code> is necessarily a type with a generic parameter, and <code>fmap</code> necessarily transforms that generic parameter, Rust's <code>Functor</code> is a lot more like <code>Biplate</code>, in that you can specialize the transform to only be over certain inner elements!</p><p>When I realized this, I started trying to shoehorn everything into it. I didn't have to just write one <code>impl&lt;L: Location> Functor&lt;L> for Program&lt;L></code> implementation, I could write multiple<sup class=footnote-ref><a data-footnote-ref href=#fn-4 id=fnref-4>5</a></sup>!</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>Location</span><span class=punctuation>&lt;</span><span class=type>L</span><span class=punctuation>>(</span><span class=type>L</span><span class=punctuation>);</span> <span class=comment>// Needs to be a newtype so we can avoid potentially conflicting implementations</span>
<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>Input</span><span class=punctuation>,</span> <span class=type>Output</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Location</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>>></span> <span class=keyword>for</span> <span class=type>Program</span><span class=punctuation>&lt;</span><span class=type>Input</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>Location</span><span class=punctuation>&lt;</span><span class=type>Input</span><span class=punctuation>>;</span>
    <span class=keyword>type</span> <span class=type>Output</span> = <span class=type>Location</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>Program</span><span class=punctuation>&lt;</span><span class=type>Output</span><span class=punctuation>>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span> <span class=punctuation>{</span> ..<span class=punctuation>.</span><span class=property> </span><span class=punctuation>}</span><span class=property>
</span><span class=punctuation>}</span><span class=property>
</span><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>I</span><span class=punctuation>,</span><span class=property> </span><span class=type>O</span><span class=punctuation>></span><span class=property> </span><span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Expression</span><span class=punctuation>&lt;</span><span class=type>O</span><span class=punctuation>>></span><span class=property> </span><span class=keyword>for</span><span class=property> </span><span class=type>Program</span><span class=punctuation>&lt;</span><span class=type>I</span><span class=punctuation>></span><span class=property> </span><span class=punctuation>{</span><span class=property> ... </span><span class=punctuation>}</span><span class=property>
</span><span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>I</span><span class=punctuation>,</span><span class=property> </span><span class=type>O</span><span class=punctuation>></span><span class=property> </span><span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Statement</span><span class=punctuation>&lt;</span><span class=type>O</span><span class=punctuation>>></span><span class=property> </span><span class=keyword>for</span><span class=property> </span><span class=type>Program</span><span class=punctuation>&lt;</span><span class=type>I</span><span class=punctuation>></span><span class=property> </span><span class=punctuation>{</span><span class=property> ... </span><span class=punctuation>}</span></code></pre><p>The restrictions of Rust's trait system, and needing to specify explicit <code>Input</code>/<code>Output</code>/<code>Mapped</code> types, actually makes <code>Functor</code> <em>more</em> flexible than its Haskell counterpart! I'm using it to create a class of functions that look like:</p><pre class=syntax-highlighting><code class=language-haskell>fmapBi :: Triplate from to => (to a -> to b) -> from a -> from b
</code></pre><p>and even <em>this</em>, despite being a pretty neat extension of both <code>Functor</code> and <code>Biplate</code> in Haskell, still doesn't capture the full generality of the Rust trait. Still, for my purposes, this "best of both worlds" is all I need: the ability to transform only specific inner nodes (from <code>Biplate</code>), and the ability to change the parameterization of the container type (from <code>Functor</code>).</p><p>I'd like to write a paper on this. The authors of <code>uniplate</code> <a href=https://ndmitchell.com/downloads/paper-uniform_boilerplate_and_list_processing-30_sep_2007.pdf>wrote one</a> explaining their approach, as did the author of <a href=https://hackage.haskell.org/package/multiplate><code>multiplate</code></a> (which I don't quite understand fully, TODO <a href=https://arxiv.org/pdf/1103.2841v1>read it</a> i guess). I'm not 100% sure I have a novel idea on my hands, but at the very least, I think it's a new useful way of looking at things that I'll be happy to include in my toy compiler.</p><hr><p>In case you're wondering, "PolyWolf, why has it taken you so long to get this post out? Surely you haven't spent over two weeks just thinking?", yes I have been thinking about this for a while, but mostly in the context of writing a proper Rust <code>proc_macro</code> that creates these implementations for me, just given the AST definition. It's unfortunately much much more involved than writing a <code>macro_rules!</code><sup class=footnote-ref><a data-footnote-ref href=#fn-5 id=fnref-5>6</a></sup>, especially trying to handle all the edge-cases properly. I don't know why I waited to publish this blog post until I finished writing this new macro; I didn't even need to explain anything about it in this post lol. Leaving that for a future blog post, maybe once I publish the crate perhaps !</p><p>As always, you can check out the code at <a href=https://github.com/p0lyw0lf/pwcc>github:p0lyw0lf/pwcc</a>; it <em>sort of</em> has documentation right now, if u squint</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>The real thing's actually worse in my opinion: <code>fn transform_bi(&self, op: Arc&lt;dyn Fn(To) -> To>) -> Self</code>. You have to deal with the overhead of <code>Arc</code>, <code>dyn</code>, <em>and</em> <code>Clone</code>-ing your entire AST?? No thanks. This is actually one of the biggest motivators I have for wanting to write a paper about this, to show these operations can be done in-place, zero-copy (given a sufficiently smart compiler, I hope). <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>(fake (deragatory)), I haven't gotten that far in the book yet, so all pseudo-registers just get assigned to unique stack addresses :) <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>Again, this signature is a massive simplification. The real one cares about lifetimes, thread safety, etc. etc. all good stuff a proper Rust library needs to care about <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a></p></li><li id=fn-6><p>I'm reading this again and I'm actually not too convinced by that argument anymore. Maybe it was so I could write things like <code>TryFunctor</code> (returns a <code>Result</code>) or <code>AsyncFunctor</code> (returns an <code>impl Future</code>) without having to change the trait at all? That seems bad tho, and I should probably just write separate traits lol <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-6>↩</a></p></li><li id=fn-4><p>Given appropriate macro/specialization support, which is what the previous post was about :P <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-4>↩</a></p></li><li id=fn-5><p>I will curse dtolnay forever for making <code>syn</code>/<code>quote</code> the only way to do this, but maybe I'm just more frustated by how hard the logic is in general... <a aria-label="Back to reference 6" class=footnote-backref data-footnote-backref data-footnote-backref-idx=6 href=#fnref-5>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: On "Safe" C++]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-18-rechost-on-safe-c-/" />
      <id>https://wolfgirl.dev/blog/2024-11-18-rechost-on-safe-c-/</id>
      <published>2024-11-19T01:55:06Z</published>
      <updated>2024-11-19T01:55:06Z</updated>
      <summary><![CDATA[https://izzys.casa/2024/11/on-safe-cxx/ Important piece. Way past time someone spoke up about this & collected it all in one place.]]></summary>
      <content type="html"><![CDATA[<p>https://izzys.casa/2024/11/on-safe-cxx/</p><p>Important piece. Way past time someone spoke up about this & collected it all in one place, even if there are many tangents as a result. Good long read</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Swift *almost* does a really cool thing I want]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-13-swift-almost-does-a-really-cool-thing-i-want/" />
      <id>https://wolfgirl.dev/blog/2024-11-13-swift-almost-does-a-really-cool-thing-i-want/</id>
      <published>2024-11-13T13:36:20Z</published>
      <updated>2024-11-13T13:36:20Z</updated>
      <summary><![CDATA[On my [toy compiler](https://wolfgirl.dev/blog/2024-10-17-work-on-my-toy-compiler-is-progressing-smoothly/), I have a lot of AST manipula...]]></summary>
      <content type="html"><![CDATA[<p>On my <a href=https://wolfgirl.dev/blog/2024-10-17-work-on-my-toy-compiler-is-progressing-smoothly/>toy compiler</a>, I have a lot of AST manipulations I need to do. There is the "parsed" AST, the "IR" AST, and the "codegen" AST, all of which need various passes defined for them. Most passes boil down to "transform all nodes of a given type in-place". Now, because my ASTs are kinda big, and I have so many of them, and I have so many node types I want to map over, I <em>really</em> don't want to have to write out this mapping function by hand.</p><p>My solution for Rust was to write a "specialized <a href=https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Functor.html>Functor</a>" trait like so<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>trait</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Inner</span><span class=punctuation>></span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Output</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>;</span>
<span class=punctuation>}</span>

<span class=comment>// example of how it's implemented:</span>
<span class=keyword>impl</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>,</span> <span class=type>Inner</span><span class=punctuation>,</span> <span class=type>A</span><span class=punctuation>,</span> <span class=type>B</span><span class=punctuation>></span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Inner</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>Vec</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>></span>
<span class=keyword>where</span>
    <span class=type>T</span><span class=punctuation>:</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>Inner</span><span class=punctuation>,</span> <span class=type>Input</span>=<span class=type>A</span><span class=punctuation>,</span> <span class=type>Output</span>=<span class=type>B</span><span class=punctuation>>,</span>
<span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>A</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Output</span> = <span class=type>B</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>Vec</span><span class=punctuation>&lt;</span><span class=type>T</span><span class=punctuation>::</span><span class=type>Mapped</span><span class=punctuation>>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span> <span class=punctuation>{</span>
        <span class=variable>self</span><span class=punctuation>.</span><span class=function>into_iter</span><span class=punctuation>().</span><span class=function>map</span><span class=punctuation>(</span><span class=operator>&</span><span class=keyword>mut</span> |<span class=variable>x</span><span class=punctuation>:</span> <span class=type>T</span>| x<span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>)).</span><span class=function>collect</span><span class=punctuation>()</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span></code></pre><p>I call this a "specialized Functor" because, unlike the Haskell version, it allows for implementations on arbitrary container types (e.x. parent AST nodes), for specific inner types (e.x. child AST nodes). To give an extremely simplified example:</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>struct</span> <span class=type>Function</span> <span class=punctuation>{</span>
    <span class=property>name</span><span class=punctuation>:</span> <span class=type>String</span><span class=punctuation>,</span>
    <span class=property>body</span><span class=punctuation>:</span> <span class=type>Vec</span><span class=punctuation>&lt;</span><span class=type>Statement</span><span class=punctuation>>,</span>
<span class=punctuation>}</span>

<span class=keyword>enum</span> <span class=type>Statement</span> <span class=punctuation>{</span>
    <span class=constructor>Return</span> <span class=punctuation>{</span> <span class=property>val</span><span class=punctuation>:</span> <span class=type>isize</span> <span class=punctuation>},</span>
<span class=punctuation>}</span></code></pre><p>A hand-written <code>Functor</code> implementation for <code>isize</code> would look like</p><pre class=syntax-highlighting><code class=language-rust><span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>isize</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>isize</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Output</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span> <span class=punctuation>{</span>
        <span class=function>f</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>)</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>isize</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>Statement</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Output</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>Statement</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span> <span class=punctuation>{</span>
        <span class=keyword>match</span> <span class=variable>self</span> <span class=punctuation>{</span>
            <span class=type>Statement</span><span class=punctuation>::</span><span class=constructor>Return</span> <span class=punctuation>{</span> val <span class=punctuation>}</span> => <span class=type>Statement</span><span class=punctuation>::</span><span class=type>Return</span> <span class=punctuation>{</span>
                <span class=property>val</span><span class=punctuation>:</span> val<span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>),</span>
            <span class=punctuation>},</span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>

<span class=keyword>impl</span> <span class=type>Functor</span><span class=punctuation>&lt;</span><span class=type>isize</span><span class=punctuation>></span> <span class=keyword>for</span> <span class=type>Program</span> <span class=punctuation>{</span>
    <span class=keyword>type</span> <span class=type>Input</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Output</span> = <span class=type>isize</span><span class=punctuation>;</span>
    <span class=keyword>type</span> <span class=type>Mapped</span> = <span class=type>Program</span><span class=punctuation>;</span>
    <span class=keyword>fn</span> <span class=function>fmap</span><span class=punctuation>(</span><span class=variable>self</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=operator>&</span><span class=keyword>mut</span> <span class=keyword>impl</span> <span class=type>FnMut</span><span class=punctuation>(</span><span class=type>Self</span><span class=punctuation>::</span><span class=type>Input</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Output</span><span class=punctuation>)</span> -> <span class=type>Self</span><span class=punctuation>::</span><span class=type>Mapped</span> <span class=punctuation>{</span>
        <span class=type>Program</span> <span class=punctuation>{</span>
            <span class=property>name</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>name</span><span class=punctuation>,</span>
            <span class=property>body</span><span class=punctuation>:</span> <span class=variable>self</span><span class=punctuation>.</span><span class=property>body</span><span class=punctuation>.</span><span class=function>fmap</span><span class=punctuation>(</span>f<span class=punctuation>),</span>
        <span class=punctuation>}</span>
    <span class=punctuation>}</span>
<span class=punctuation>}</span>

<span class=attribute>#</span><span class=punctuation>[</span><span class=attribute>test</span><span class=punctuation>]</span>
<span class=keyword>fn</span> <span class=function>map_isize</span><span class=punctuation>()</span> <span class=punctuation>{</span>
    <span class=keyword>let</span> x = <span class=type>Program</span> <span class=punctuation>{</span>
        <span class=property>name</span><span class=punctuation>:</span> <span class=string>"main"</span><span class=punctuation>.</span><span class=function>into</span><span class=punctuation>(),</span>
        <span class=property>body</span><span class=punctuation>:</span> <span class=punctuation>[</span><span class=type>Statement</span><span class=punctuation>::</span><span class=type>Return</span> <span class=punctuation>{</span> <span class=property>val</span><span class=punctuation>:</span> <span class=constant>1</span> <span class=punctuation>}].</span><span class=function>into</span><span class=punctuation>(),</span>
    <span class=punctuation>};</span>
    <span class=comment>// Pretend we derived Clone</span>
    <span class=keyword>let</span> y = <span class=type>Functor</span><span class=punctuation>::&lt;</span><span class=type>isize</span><span class=punctuation>>::</span><span class=function>fmap</span><span class=punctuation>(</span>x<span class=punctuation>.</span><span class=function>clone</span><span class=punctuation>(),</span> |i| i + <span class=constant>1</span><span class=punctuation>);</span>
    <span class=comment>// Pretend we derived PartialEq</span>
    <span class=macro>assert_eq!</span><span class=punctuation>(</span>y<span class=punctuation>,</span> <span class=constructor>Program</span> <span class=punctuation>{</span>
        <span class=property>name</span><span class=punctuation>:</span> <span class=string>"main"</span><span class=punctuation>.</span><span class=function>into</span><span class=punctuation>(),</span>
        <span class=property>body</span><span class=punctuation>:</span> <span class=punctuation>[</span><span class=type>Statement</span><span class=punctuation>::</span><span class=constructor>Return</span> <span class=punctuation>{</span> <span class=property>val</span><span class=punctuation>:</span> <span class=constant>2</span> <span class=punctuation>}].</span><span class=function>into</span><span class=punctuation>(),</span>
    <span class=punctuation>});</span>
<span class=punctuation>}</span></code></pre><p>That is, we re-construct the value in-place, recursively calling <code>fmap</code> to transform the appropriate fields<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>2</a></sup>.</p><p>Now, because the entire point of this is for me to be really lazy, I did not want to have to write out all these <code>Functor</code> implementations manually. So naturally, I <a href=https://github.com/p0lyw0lf/pwcc/blob/6598df9aa48c28b4fe577540e7d824343510670f/functional/src/functor.rs>wrote a macro</a><sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>3</a></sup> that does most of the work for me. All I have to do now is specify, for a given inner type, for each AST node above it in the tree, what fields I'd like to map over.</p><p>Unfortunately, that's still too much work!!! I have a lot of AST nodes and inner types I want to map over, so what I'd really like is a way to define something like this:</p><pre class=syntax-highlighting><code class=language-swift><span class=keyword>func</span> <span class=function>fmap</span><span class=operator>&lt;</span><span class=variable>Container</span><span class=punctuation>,</span> <span class=variable>Inner</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>over</span> <span class=variable>c</span><span class=punctuation>:</span> <span class=type>Container</span><span class=punctuation>,</span> <span class=variable>f</span><span class=punctuation>:</span> <span class=punctuation>(</span><span class=type>Inner</span><span class=punctuation>)</span> <span class=operator>-></span> <span class=type>Inner</span><span class=punctuation>)</span> <span class=operator>-></span> <span class=type>Container</span> <span class=punctuation>{</span>
  <span class=comment>// somehow `fmap` over all of `c`'s fields</span>
  <span class=keyword>return</span> <span class=variable>c</span>;
<span class=punctuation>}</span>

<span class=keyword>func</span> <span class=function>fmap</span><span class=operator>&lt;</span><span class=variable>Inner</span><span class=operator>></span><span class=punctuation>(</span><span class=variable>over</span> <span class=variable>v</span><span class=punctuation>:</span> <span class=type>Inner</span><span class=punctuation>,</span> <span class=variable>f</span> <span class=punctuation>(</span><span class=variable>Inner</span><span class=punctuation>)</span> <span class=operator>-></span> <span class=type>Inner</span><span class=punctuation>)</span> <span class=operator>-></span> <span class=type>Inner</span> <span class=punctuation>{</span>
  <span class=keyword>return</span> <span class=function>f</span><span class=punctuation>(</span><span class=variable>v</span><span class=punctuation>)</span>;
<span class=punctuation>}</span></code></pre><p>Basically: if we're at the target node type, just call the function (definition 2), otherwise, recursively call into all fields (definition 1). Easy as pie!</p><p>Wait a minute... that syntax highlighting... yep, this "pseudocode" is in fact, valid Swift! It even does exactly we want!!! See?</p><pre class=syntax-highlighting><code class=language-swift><span class=function>print</span><span class=punctuation>(</span><span class=function>fmap</span><span class=punctuation>(</span><span class=number>1</span><span class=punctuation>,</span> <span class=punctuation>{</span> <span class=variable>$0</span> <span class=operator>+</span> <span class=number>1</span> <span class=punctuation>}))</span>; <span class=comment>// 2</span>
<span class=function>print</span><span class=punctuation>(</span><span class=function>fmap</span><span class=punctuation>(</span><span class=string>"hello"</span><span class=punctuation>,</span> <span class=punctuation>{</span> <span class=variable>$0</span> <span class=operator>+</span> <span class=number>1</span> <span class=punctuation>}))</span>; <span class=comment>// "hello"</span>
<span class=function>print</span><span class=punctuation>(</span><span class=function>fmap</span><span class=punctuation>(</span><span class=string>"hello"</span><span class=punctuation>,</span> <span class=punctuation>{</span> <span class=variable>$0</span> <span class=operator>+</span> <span class=string>"_world"</span> <span class=punctuation>}))</span>; <span class=comment>// "hello_world"</span></code></pre><p>This kind of definition is impossible to do in Rust thanks to a little thing called <a href="https://doc.rust-lang.org/reference/items/implementations.html?highlight=coherence#trait-implementation-coherence">Coherence</a>, a.k.a. No <a href=https://rust-lang.github.io/rfcs/1210-impl-specialization.html>Specialization</a>. What Specialization allows us to do is write down those 2 definitions for <code>fmap</code>, and the compiler will automatically choose the "least generic" one according to some rules.</p><p>Great! Now all I have to do is implement some compile-time introspection to get it to map over the appropriate fields. How hard could that be?</p><p>[2 hours of trying to read Swift documentation and finding mostly AI-generated blog posts. Thank goodness for StackOverflow at least]</p><p>Upon looking further, it seems the built-in read-only reflection with <a href=https://developer.apple.com/documentation/swift/mirror>Mirror</a> only happens at runtime, and the <a href=https://github.com/wickwirew/Runtime>Runtime</a> read-write reflection library is same way (aptly-named). Also, there doesn't seem to be a way to dynamically call <code>fmap</code> based on the runtime type; instead, all recursive calls result in an <code>Any</code> type for <code>Container</code>, since that's all that can be resolved statically. By passing in types as arguments, I can get all the way down to "cannot convert value of type 'any Any.Type' to expected argument type '(any Any).Type'", which really illustrates the predicament.</p><p>I <em>might</em> be able to hack something with the <code>dynamic</code> keyword or like Protocols or whatever, but I'm still not pleased about runtime-only reflection. I am willing to pay the slight extra binary cost for all these definitions, and Swift doesn't let me do that. Unfortunate that this was a dead end, but still pretty fun to learn something new!</p><p>Think I'll try Haskell next? :P</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Note this trait has extra considerations for "what if the thing we're mapping over is polymorphic?". The complexity is about the same even without that consideration, unfortunately... <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-3><p>Note the strange function invocation outside the trait. The compiler has a hard time inferring what <code>Functor</code> implementation to use otherwise, for some reason. That's how you know we're doing something cursed! <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-3>↩</a></p></li><li id=fn-2><p>(Not sure where to put this but it's still relevant for footnotes) I also had the idea of using this macro to define both <code>Functor&lt;Inner></code> and <code>Functor&lt;Result&lt;Inner, Err>></code> implementations. This works because <code>Inner</code> is never a <code>Result</code>, so everything has coherence! These latter implementations would have <code>Input=Inner, Output=Result&lt;Inner, Err></code> and could be configured to fail early or to collect all errors. Unfortunately, I ran into Coherence problems again when I tried to <code>impl Functor&lt;Result&lt;T, E>> for Vec&lt;Result&lt;T, E>></code> (for <code>Mapped=Result&lt;Vec&lt;T, E>></code>). I <em>could</em> just write another trait as well, but man that's annoying! <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[What If Anyone* Could Use My Post Composer?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-08-what-if-anyone-could-use-my-post-composer-/" />
      <id>https://wolfgirl.dev/blog/2024-11-08-what-if-anyone-could-use-my-post-composer-/</id>
      <published>2024-11-08T18:22:28Z</published>
      <updated>2024-11-08T18:22:28Z</updated>
      <summary><![CDATA[A somewhat-common complaint I see on bsky/fedi is "gah, these darn character limits, if only i had somewhere to jot down longer posts..."]]></summary>
      <content type="html"><![CDATA[<p>A somewhat-common complaint I see on bsky/fedi is "gah, these darn character limits, if only i had somewhere to jot down longer posts, i would be incentivized to post to my blog most often. alas, i have to open a text editor on my computer and fumble around with <code>git</code>, which is simply too much work."</p><p>Which, like, fair! I actually had this exact same issue. And then, in the spirit of <a href=https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/>Write Your Own Tools</a>, I wrote my own post composer, as pictured here:</p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/01930c42-0ba7-7ff1-a6f4-8a540781bd7d/IMG_0208.jpeg></p><p>I've continued to add features since I first started using it too: drafts (with auto-save!), uploading attachments, and previews were all not part of the initial conception. But, because I Wrote My Own Tool, every time I felt it was lacking something, I could just add it.</p><p>Now that's great n all, but back to the original point: I started thinking, since I have this really neat tool that works well for my usecase, might other people find it suitable for their usecases too? How would I go about "productionizing" it? So here I am, brainstorming & writing down some proposed restrictions that would make it possible for me to do so in a fairly short timeframe:</p><ol><li>Users will log in with GitHub<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup></li><li>Users will select a repository + branch + path they want to "post" to, and will have the ability to customize the filename and content according to templates (instructions/examples included)</li><li>Users will NOT have the ability to automatically upload attachments, like I do</li><li>Users will NOT have the ability to automatically make announcements on bksy/fedi, like I do</li></ol><p>These restrictions assume a workflow of "uses GitHub Actions to automatically update a website whenever a commit is added"<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>, which I'm assuming is at least somewhat possible for the majority of people who would be interested in this tool (doesn't really help if you don't have automatic deploys set up already).</p><p>And now I reveal the true reason for this post: to gauge interest before I embark on changing my janky code to be slightly less janky for others to use. If you're reading this, and would be interested in using this tool, please leave a comment/contact me with something that includes the phrase "i'm interested in using your post composer". If the above restrictions preclude you from using this, please also let me know somehow. And as always, thanks for reading &lt;3</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>This is the part I see the most people getting hung up on, b/c who's gonna trust a random person they've never met with "ability to act on your behalf on private repos" oauth permissions?? i sure wouldn't <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>More security-conscious people may object to this as well, given GitHub's historically lax security regarding exposing repository secrets to workflows. They have been getting better tho! <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Rechost: EX-LYD & Sunnexo - Hyper Link]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-08-rechost-ex-lyd-sunnexo-hyper-link/" />
      <id>https://wolfgirl.dev/blog/2024-11-08-rechost-ex-lyd-sunnexo-hyper-link/</id>
      <published>2024-11-08T14:45:38Z</published>
      <updated>2024-11-08T14:45:38Z</updated>
      <summary><![CDATA[<https://sunnexo.moe/hyperlink>  you may recall from [a previous blog post](https://wolfgirl.dev/blog/2024-10-04-bandcamp-friday-haul/)...]]></summary>
      <content type="html"><![CDATA[<p><a href=https://sunnexo.moe/hyperlink>https://sunnexo.moe/hyperlink</a></p><p>you may recall from <a href=https://wolfgirl.dev/blog/2024-10-04-bandcamp-friday-haul/>a previous blog post</a> that I am a fan of Sunnexo, and also a fan of their devlogs where they describe what goes into their piece! It's a good song, very cool to get a glimpse into how it was produced, b/c i don't know a whole lot about that sorta stuff lol</p><p>(also while on the topic of music i should mention, i listened to Tanger's latest album, <a href=https://tangermusic.bandcamp.com/album/prefer-not-to-say>Prefer not to say</a>, and it's <em><strong>really frickin good</strong></em> so yeah give that a listen too)</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[going thru it rn]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-06-going-thru-it-rn/" />
      <id>https://wolfgirl.dev/blog/2024-11-06-going-thru-it-rn/</id>
      <published>2024-11-06T17:23:00Z</published>
      <updated>2024-11-06T17:23:00Z</updated>
      <summary><![CDATA[if you've followed me for long enough, you'll know i'm not one for big statements around big events. however, this event is simply too bi...]]></summary>
      <content type="html"><![CDATA[<p>if you've followed me for long enough, you'll know i'm not one for big statements around big events. however, this event is simply too big to ignore.</p><p>there will be grief today, there will be grief tomorrow, there will be grief when he formally assumes power, there will be grief WHEN (not if) he starts to come after you & the people you love. a tragedy beyond words that we'll live thru this history.</p><p>but we have to keep living. that's how we win. i'm currently fighting off another bout of covid, taking things one step at a time (again). i am constantly reminding myself, and hopefully you too: this is not how it ends. take care yoself, i love you</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I did my first V6 boulder today!!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-11-02-i-did-my-first-v6-boulder-today-/" />
      <id>https://wolfgirl.dev/blog/2024-11-02-i-did-my-first-v6-boulder-today-/</id>
      <published>2024-11-02T17:54:02Z</published>
      <updated>2024-11-02T17:54:02Z</updated>
      <summary><![CDATA[![a bouldering route that starts on a volume with small footholds you have to leap from, into a slightly awkward finish](https://static.w...]]></summary>
      <content type="html"><![CDATA[<p><img alt="a bouldering route that starts on a volume with small footholds you have to leap from, into a slightly awkward finish" src=https://static.wolfgirl.dev/polywolf/blog/0192edf3-ed9b-799a-906c-e75d4a8c4734/IMG_7744.jpeg></p><p>this orange one. I got the start pretty easy once I saw someone do it, the trick on the leap is to actually go closer to the wall since that's better, and then the top is just getting the right leg up into a knee bar & sitting on it. Fairly short all things considered, def on the easier side of V6es, but still, I did it!!</p><p>I've also done a few more V5s since my posts on Cohost, but after doing three there's less cause for celebration on each. There are two more V5s in this gym that are solidly in-reach for me, just need to stick the endings.</p><p>Also, there was this really cool blue V6 on this same wall from the set before the orange:</p><p><img alt="a footwork-heavy bouldering route with two long horizontal sections & a focus on using your arms to push down" src=https://static.wolfgirl.dev/polywolf/blog/0192edf3-ed9b-799a-906c-e75d4a8c4734/IMG_7524.jpeg></p><p>which was a great workout. I really liked the moves for it & almost completed it, but the end was always too scary after the exhausting middle. Fortunately, whoever sets this wall still tends to set climbs in my style, so I got one in the end anyways :)</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I'm Streaming Factorio: Space Age]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-28-i-m-streaming-factorio-space-age/" />
      <id>https://wolfgirl.dev/blog/2024-10-28-i-m-streaming-factorio-space-age/</id>
      <published>2024-10-29T01:28:25Z</published>
      <updated>2024-10-29T01:28:25Z</updated>
      <summary><![CDATA[making this a daily thing, i think. just a couple hours each evening. p chill, p scuffed stream setup, hop by if ur interested!]]></summary>
      <content type="html"><![CDATA[<p>making this a daily thing, i think. just a couple hours each evening. p chill, p scuffed stream setup, hop by if ur interested! <a href=https://twitch.tv/p0lyw0lf>https://twitch.tv/p0lyw0lf</a></p><p>Also, if you're a friend, and you want to join the server, DM me and I'll give you the IP/password</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[You'll Never Guess How I Fixed My Networking Problem]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-24-you-ll-never-guess-how-i-fixed-my-networking-problem/" />
      <id>https://wolfgirl.dev/blog/2024-10-24-you-ll-never-guess-how-i-fixed-my-networking-problem/</id>
      <published>2024-10-24T20:05:12Z</published>
      <updated>2024-10-24T20:05:12Z</updated>
      <summary><![CDATA[For reference, here's my previous network topology:  Verizon -(fiber)-> fiber-to-ethernet box -(ethernet)-> router -(coax)-> wifi exten...]]></summary>
      <content type="html"><![CDATA[<p>For reference, here's my previous network topology:</p><p>Verizon -(fiber)-> fiber-to-ethernet box -(ethernet)-> router -(coax)-> wifi extender -(ethernet)-> switch -(ethernet)-> my computer</p><p>Yeah, it's pretty cursed, I know.<details><summary>Here's the rationale for anyone interested:</summary> <p>The fiber comes in thru a network closet, so the fiber-to-ethernet box has to be there. However, the router can't fit in the closet, so it has to go in a room somewhere. Bedrooms are straight out, so this means we have to use the single wall ethernet jack in the living room to get internet to the router.</p> <p>But then! Now we have no way of getting internet back out to the other ethernet jacks, because putting a switch before the router causes the fiber-to-internet box to get confused as to which one is the router. So, we used the coax port conveniently placed right next to the ethernet port to run a signal thru the walls to the bedroom with the weakest wifi, and then an ethernet port on the wifi extender can connect to the ethernet jack on that wall, and then a simple switch in the network closet can share ethernet with all the other rooms.</p> <p>Got all that? Good.</p></details><p>For a while, I would have sporadic 30s drops in internet connection, but only while plugged into ethernet; wifi was much more stable (tho, congested due to the existence of other apartments, esp uploads).</p><p>Now just think about this for a second. What in this chain could <em>possibly</em> exhibit such behavior? The most likely suspects, Verizon, fiber-to-ethernet, & the router are all ruled out by the fact that wifi always works. And everything else seems like it falls into the category of "either it works 100% of the time or 0% of the time"<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>1</a></sup>.</p><p>The next most likely suspect was my dinky ethernet-to-usb adapter I was using. But more experimentation revealed these drops happened across multiple adapters (2), across multiple machines (Windows & Mac laptops), with and without USB hubs, etc. I was stumped, and resolved to be just another WiFi Warrior.</p><p>Then, I offhandedly mentioned to my roommate I never used ethernet, and when quizzed as to why, <strong>they too</strong> said they noticed sporadic 30s drops. Both of us hadn't realized we were running into the exact same problem and assumed it was just something with our own hardware, not the common chain! Deciding to get to the bottom of this once and for all, we figured -(coax)-> wifi extender was the weakest link in the chain<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>2</a></sup>, and removed it to create a new topology:</p><p>Verizon -(fiber)-> fiber-to-ethernet box -(ethernet)-> router -(ethernet)-> switch in my room -(ethernet)-> switch in network closet -(ethernet)-> roommate's computer</p><p>The router has to stay in the living room, so running a cable from it to my room has to go under the door, which is pretty silly<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>3</a></sup>. One cable then goes to the ethernet jack in my room (which leads to the closet), and another goes to my computer. There are many hops in this diagram, but it all seems to work flawlessly! Who knew that a wifi extender couldn't be trusted to act as a coax-to-ethernet converter :)</p><section class=footnotes data-footnotes><ol><li id=fn-3><p>well i mean, <em>maybe</em> the switches, but like, really? i can't see how those fail for that long outside of like mac address spoofing attacks, which isn't happening on our LAN full of trusted devices. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-3>↩</a></p></li><li id=fn-1><p>we both have basic networking experience and saw no reason to distrust any of the ethernet cables or switches we provided. So eliminating unknown variables made sense. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-1>↩</a></p></li><li id=fn-2><p><img alt="ethernet cable running under a door onto my desk into a switch, with two more cables running off the desk" src=https://static.wolfgirl.dev/polywolf/blog/0192bf3c-a00d-7119-a205-26fd235172a9/IMG_7699.jpeg> <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[RIT Pipe Graffiti Pictures]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-20-rit-pipe-graffiti-pictures/" />
      <id>https://wolfgirl.dev/blog/2024-10-20-rit-pipe-graffiti-pictures/</id>
      <published>2024-10-20T20:17:36Z</published>
      <updated>2024-10-20T20:17:36Z</updated>
      <summary><![CDATA[I took these pictures while visiting a friend at RIT. They showed me around a bunch of places, and I think these pipes are the absolute c...]]></summary>
      <content type="html"><![CDATA[<p>I took these pictures while visiting a friend at RIT. They showed me around a bunch of places, and I think these pipes are the absolute coolest.</p><p>Also, I added a file upload feature to my post composer! This was partly because I was too lazy to connect my phone to my computer to upload these images, but also because that laziness is a good excuse to put in more effort now so i can be even lazier later :P Now I just gotta work on the post previewer so I can actually see them before I post...</p><p>There's a whole rigamarole with IAM permissions for S3 (create policy -> attach policy to group -> add user to group -> create access keys for user) and Nginx not liking file uploads by default (<code>client_max_body_size</code>) but honestly that's par for course. Anyways, you're here for images, so here's images:</p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7624.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7627.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7629.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7630.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7631.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7633.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7634.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7635.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7636.jpeg></p><p><img alt src=https://static.wolfgirl.dev/polywolf/blog/0192ab89-cf33-7447-a8d5-1dd1fbeaf31c/IMG_7637.jpeg></p><p>Neat, huh? <del>sorry about the lack of alt text, I'll get to that later maybe</del></p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[More Automatic Crossposting!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-18-more-automatic-crossposting-/" />
      <id>https://wolfgirl.dev/blog/2024-10-18-more-automatic-crossposting-/</id>
      <published>2024-10-19T03:16:22Z</published>
      <updated>2024-10-19T03:16:22Z</updated>
      <summary><![CDATA[I crossposted my [last blog post](https://wolfgirl.dev/blog/2024-10-17-work-on-my-toy-compiler-is-progressing-smoothly/) to my [Mastodon]...]]></summary>
      <content type="html"><![CDATA[<p>I crossposted my <a href=https://wolfgirl.dev/blog/2024-10-17-work-on-my-toy-compiler-is-progressing-smoothly/>last blog post</a> to my <a href=https://social.treehouse.systems/@PolyWolf>Mastodon</a> and <a href=https://bsky.app/profile/wolf.girl.technology>Bluesky</a> accounts, because I have no idea how much traffic my RSS feed is getting, and seeing funny numbers go up in response to people acknowledging my post feels good. I then realized, "hey wait a minute, I have <a href=https://github.com/p0lyw0lf/crossposter>this whole framework</a> for publishing to multiple platforms, which I'm using for this blog, and already have a Mastodon backend, so why don't I just hook that up too?" And then I hooked it up and all was well :)</p><p>...except, that isn't the entire story. I did not have a Bluesky backend before tonight, and adding one turned out to be a bit trickier than I intended.</p><h3><a aria-hidden=true class=anchor href=#the-easy-part id=heading-the-easy-part></a>The Easy Part</h3><p>Authentication was a lot simpler than Mastodon; Bluesky just uses password login. I'm fortunate there's a Python client for <a href=https://pypi.org/project/atproto/>atproto</a>, and my secrets system can handle sensitive data like passwords just fine, so hooking it in was no issue.</p><h3><a aria-hidden=true class=anchor href=#the-unexpectedly-hard-part id=heading-the-unexpectedly-hard-part></a>The Unexpectedly Hard Part</h3><p>Bluesky has pretty minimal rich text formatting: just links, hash-tags, & at-mentions. All I really wanted was links, and thought I could just drop in a URL into the post body & Bluesky clients would automatically make it clickable, just like Mastodon clients seem to do. Nope! Turns out, atproto has some sort of out-of-band (i.e. not contained in the text itself) data called "facets" for this purpose. Meaning I somehow had to construct a <a href=https://atproto.blue/en/latest/atproto_client/utils/text_builder.html#atproto_client.utils.text_builder.TextBuilder><code>TextBuilder</code></a> object with the link to my post formatted the way I desired.</p><p>If I were willing to hardcode things, this might have been easy. Unfortunately, I don't accept such half-measures. The rest of my backends work by running Jinja2 over a text file and then pushing that elsewhere, why shouldn't I be able to do the same with Bluesky? So I figured out a way. What my slowly-becoming-more-sleep-addled-by-the-second brain came up with was to use the <a href=https://github.com/miyuchina/mistletoe>mistletoe</a> markdown parsing library to traverse a markdown AST into a <code>TextBuilder</code>, like so:</p><pre class=syntax-highlighting><code class=language-python><span class=keyword>from</span> <span class=variable>atproto</span> <span class=keyword>import</span> <span class=variable>client_utils</span>
<span class=keyword>import</span> <span class=variable>mistletoe</span>
<span class=keyword>from</span> <span class=variable>mistletoe</span>.<span class=variable>base_renderer</span> <span class=keyword>import</span> <span class=constructor>BaseRenderer</span>

<span class=keyword>class</span> <span class=constructor>BlueskyRenderer</span>(<span class=constructor>BaseRenderer</span>):
    <span class=keyword>def</span> <span class=function>__init__</span>(<span class=variable>self</span>, <span class=operator>*</span><span class=variable>args</span>, <span class=operator>**</span><span class=variable>kwargs</span>):
        <span class=function>super</span>().<span class=property>__init__</span>(<span class=operator>*</span><span class=variable>args</span>, <span class=operator>**</span><span class=variable>kwargs</span>)
        <span class=variable>self</span>.<span class=property>builder</span> <span class=operator>=</span> <span class=variable>client_utils</span>.<span class=property>TextBuilder</span>()
        <span class=variable>self</span>.<span class=property>has_paragraph</span> <span class=operator>=</span> <span class=constant>False</span>

    <span class=keyword>def</span> <span class=function>render_inner</span>(<span class=variable>self</span>, <span class=variable>token</span>):
        <span class=keyword>for</span> <span class=variable>token</span> <span class=operator>in</span> <span class=variable>token</span>.<span class=property>children</span>:
            <span class=variable>self</span>.<span class=property>render</span>(<span class=variable>token</span>)
        <span class=keyword>return</span> <span class=variable>self</span>.<span class=property>builder</span>

    <span class=keyword>def</span> <span class=function>render_raw_text</span>(<span class=variable>self</span>, <span class=variable>token</span>):
        <span class=keyword>return</span> <span class=variable>self</span>.<span class=property>builder</span>.<span class=property>text</span>(<span class=variable>token</span>.<span class=property>content</span>)

    <span class=keyword>def</span> <span class=function>render_line_break</span>(<span class=variable>self</span>, <span class=variable>token</span>):
        <span class=keyword>return</span> <span class=variable>self</span>.<span class=property>builder</span>.<span class=property>text</span>(<span class=string>"\n"</span>)

    <span class=keyword>def</span> <span class=function>render_paragraph</span>(<span class=variable>self</span>, <span class=variable>token</span>):
        <span class=keyword>if</span> <span class=variable>self</span>.<span class=property>has_paragraph</span>:
            <span class=variable>self</span>.<span class=property>builder</span>.<span class=property>text</span>(<span class=string>"\n\n"</span>)
        <span class=variable>self</span>.<span class=property>has_paragraph</span> <span class=operator>=</span> <span class=constant>True</span>
        <span class=keyword>return</span> <span class=variable>self</span>.<span class=property>render_inner</span>(<span class=variable>token</span>)

    <span class=keyword>def</span> <span class=function>render_link</span>(<span class=variable>self</span>, <span class=variable>token</span>):
        <span class=keyword>return</span> <span class=variable>self</span>.<span class=property>builder</span>.<span class=property>link</span>(<span class=variable>token</span>.<span class=property>title</span>, <span class=variable>token</span>.<span class=property>target</span>)

    <span class=keyword>def</span> <span class=function>render_auto_link</span>(<span class=variable>self</span>, <span class=variable>token</span>):
        <span class=keyword>return</span> <span class=variable>self</span>.<span class=property>builder</span>.<span class=property>link</span>(<span class=variable>token</span>.<span class=property>target</span>, <span class=variable>token</span>.<span class=property>target</span>)

<span class=keyword>def</span> <span class=function>render_post</span>(<span class=variable>post</span>: <span class=type>str</span>) <span class=operator>-></span> <span class=variable>client_utils</span>.<span class=property>TextBuilder</span>:
    <span class=keyword>return</span> <span class=variable>mistletoe</span>.<span class=property>markdown</span>(<span class=variable>post</span>, <span class=constructor>BlueskyRenderer</span>)</code></pre><p>This is a bit janky, since the <code>render_*</code> functions are supposed to return a <code>str</code>, not a <code>TextBuilder</code>, but Python is dynamically typed so it all works out! Hopefully you've seen this for yourself, having been directed from one of the platforms I've crossposted this to :)</p><p><a href=https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/>Write Your Own Tools</a> philosophy strikes again!!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[work on my toy compiler is progressing smoothly]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-17-work-on-my-toy-compiler-is-progressing-smoothly/" />
      <id>https://wolfgirl.dev/blog/2024-10-17-work-on-my-toy-compiler-is-progressing-smoothly/</id>
      <published>2024-10-18T02:23:46Z</published>
      <updated>2024-10-18T02:23:46Z</updated>
      <summary><![CDATA[as mentioned in my [projects](/projects/) page, i'm writing a toy compiler called [PolyWolf's C Compiler](https://github.com/p0lyw0lf/pwc...]]></summary>
      <content type="html"><![CDATA[<p>as mentioned in my <a href=/projects/>projects</a> page, i'm writing a toy compiler called <a href=https://github.com/p0lyw0lf/pwcc>PolyWolf's C Compiler</a> based off the book <a href=https://nostarch.com/writing-c-compiler>Writing a C Compiler</a>. I've used it as an excuse to do a good amount of Rust metaprogramming (<code>macro_rules!</code> only!!), and now that I've figured out a bunch of the tricky bits, it's been smooth sailing putting more recent chapters into my mostly-declarative framework.</p><p>my approach diverges a bit from the book, however, even by chapter 4 (out of many, many more) and I think that's important to write down. <code>wacc</code> does not distinguish between "all possible operands" and "all operands that can be written to", making it possible to represent illegal instructions like <code>mov $1, $2</code>. <code>pwcc</code> <em>does</em> make a distinction between these, respectively calling them "Val" & "Temporary" at the IR stage and "Operand" & "Location" at the assembly generation stage.</p><p>however, this doesn't resolve <em>all</em> issues. memory-to-memory <code>mov</code>s still aren't allowed & need to be dealt with the same way, by introducing an intermediate register<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>. it also has created more problems, like needing to allocate extra Temporaries in certain scenarios the result of an expression is a Val, when the original code just uses a register instead of stack space. my hope is that my structure is amenable to optimizations later, plus it just feels better to have more invalid states not representable via types :) I do wonder if I can make <em>all</em> invalid instructions not representable, i.e. by also distinguishing between register and memory operands earlier... maybe not worth it b/c better to consolidate that logic into the hardware pass at the end instead of forcing it to be in the IR -> Assembly pass? idk we'll see</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>i don't feel great about this solution; the book allocates an entire <strong><em>2</em></strong> registers for this purpose, <code>%r10d</code> and <code>%r11d</code>, which i think is way too much for the limited number x86 has. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[New Blog Styling]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-05-new-blog-styling/" />
      <id>https://wolfgirl.dev/blog/2024-10-05-new-blog-styling/</id>
      <published>2024-10-05T21:09:48Z</published>
      <updated>2024-10-05T21:09:48Z</updated>
      <summary><![CDATA[Breakpoints! Light/Dark mode!! Actually readable long-form text!!! You can really see the Cohost inspiration. Still tried to keep it some...]]></summary>
      <content type="html"><![CDATA[<p>Breakpoints! Light/Dark mode!! Actually readable long-form text!!! You can really see the Cohost inspiration. Still tried to keep it somewhat my own style, with no <code>border-radius</code> & my own colorscheme (not that I'm very good at designing colorschemes, lol). Much happier with it now :3</p><p>Speaking of Cohost, today's the day I finally start to work thru my following list to make sure I'm keeping up with ppl as they move on to various places. I did a pretty OK job right after the closure announcement, but still need to confirm. Just to get a sense of closure. I'll wait until I receive the full data dump to actually start moving posts over.</p><p>Short post, not a lot to say today, see y'all next time!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Bandcamp Friday Haul (#1)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-04-bandcamp-friday-haul/" />
      <id>https://wolfgirl.dev/blog/2024-10-04-bandcamp-friday-haul/</id>
      <published>2024-10-05T01:44:47Z</published>
      <updated>2024-10-05T01:44:47Z</updated>
      <summary><![CDATA[Usually, my music workflow goes like this:  1. Discover new music on ~~Spotify~~ Deezer, sometimes YouTube 2. Keep a "canonical" list...]]></summary>
      <content type="html"><![CDATA[<p>Usually, my music workflow goes like this:</p><ol><li>Discover new music on <del>Spotify</del> Deezer, sometimes YouTube</li><li>Keep a "canonical" list of songs I like in a YouTube playlist, transferring over my Deezer likes every once in a while</li><li>Once that list grows to about 100-150 songs, prune it.</li><li>If the list remains large, purchase all the songs (Bandcamp > iTunes > not paying)</li></ol><p>However, the sporadic nature of this process means the purchasing time rarely overlaps with a Bandcamp Friday. Since nowadays, Bandcamp Fridays are so rare, I figured I'd get in on this one while I still can.</p><p>I won't be covering <em>all</em> the albums I purchased (there are a bit too many to list, you can see them on my <a href=https://bandcamp.com/p0lyw0lf>bandcamp page</a> if u really want), and will instead be highlighting a few I think deserve special mention.</p><blockquote><p>NOTE: due to time pressures, I have not listened to all albums in full, these are just my first impressions</p></blockquote><h2><a aria-hidden=true class=anchor href=#emplexx---hiraeth id=heading-emplexx---hiraeth></a><a href=https://emplexx.bandcamp.com/album/hiraeth>Emplexx - Hiraeth</a></h2><p>Really happy to see this one come out! I've been following their work for a while, they've gone thru some rough patches, so it's nice to see an album representing work they feel proud of. And boy should they; this is, in my opinion, genre-defining work in complextro. "LUV U" is one of my favorite songs ever, and the rest seem of equal quality. Good album.</p><h2><a aria-hidden=true class=anchor href=#sunnexo--tanger---any-ep id=heading-sunnexo--tanger---any-ep></a><a href=https://sunnexo.bandcamp.com/album/any-ep>Sunnexo & Tanger - Any% EP</a></h2><p>I really like Sunnexo's style. Very chiptune-y, bit dubstep-y, nice video-game-inspired music that is very catchy. They have a <a href=https://sunnexo.moe/any>devlog</a>! Good EP.</p><h2><a aria-hidden=true class=anchor href=#camoufly---in-plain-sight id=heading-camoufly---in-plain-sight></a><a href=https://camouflybeats.bandcamp.com/album/in-plain-sight>camoufly - In Plain Sight</a></h2><p>Bit more laid-back than the other albums, but still great energy in these. Dance-able beats, neat instrumentation, all the drops are so groovy. idk how to describe it, it's just a good EP.</p><h2><a aria-hidden=true class=anchor href=#andrew-huang--undulae---labyrinth id=heading-andrew-huang--undulae---labyrinth></a><a href=https://andrewhuang.bandcamp.com/album/labyrinth>Andrew Huang & Undulae - Labyrinth</a></h2><p>I don't do experimental music too much, but this album might just convince me! I liked these songs a lot more than I would think at first glance. Andrew Huang works his magic once again, good album.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Sharing My RSS Feeds]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-02-sharing-my-rss-feeds/" />
      <id>https://wolfgirl.dev/blog/2024-10-02-sharing-my-rss-feeds/</id>
      <published>2024-10-03T00:34:41Z</published>
      <updated>2024-10-03T00:34:41Z</updated>
      <summary><![CDATA[I have been having a jolly old time subscribing to people's blogs, new & old, w/ RSS. I can't believe I missed out on this for so long, i...]]></summary>
      <content type="html"><![CDATA[<p>I have been having a jolly old time subscribing to people's blogs, new & old, w/ RSS. I can't believe I missed out on this for so long, it's really nice to get updates directly from the source w/o having to wade thru social media first. Having a dedicated "place" for reading long-form helps too.</p><p>My setup is a FreshRSS server (self-hosted) that I read with NetNewsWire (iOS). This works spectacularly and I imagine I'll be doing this for years to come.</p><p>A particularly cool thing about FreshRSS is the ability to share feeds. In lieu of support for that built into this blog (we all know <a href=https://wolfgirl.dev/cybersec/>https://wolfgirl.dev/cybersec/</a> is a half-measure), I think this is the next best thing.</p><p>A "dynamic OPML" of all the blogs I follow can be found <a href="https://freshrss.wolfgirl.dev/api/query.php?user=polywolf&t=1QZGHzuQLRcRdthNi7H22B&f=opml">here</a>. How it works is, your RSS client will subscribe to my list, and then for each site on the list, subscribe to it. In case that doesn't work, you can subscribe to just the amalgamation of RSS <a href="https://freshrss.wolfgirl.dev/api/query.php?user=polywolf&t=1QZGHzuQLRcRdthNi7H22B&f=rss">here</a>, but I think that's a poorer experience since every time I add a new site it'll clog up the most recently added posts.</p><p>In case you don't feel like drinking from the firehose, I also have a feed that's just the articles I extremely super extra enjoy, <a href="https://freshrss.wolfgirl.dev/api/query.php?user=polywolf&t=264eQGcy4W52VYwdw9DfsD&f=rss">here</a>. Unfortunately, NetNewsWire doesn't have support for labels, so I can't really be any less selective about what I re-share, sorry!</p><hr><p>It's a bit strange not getting any feedback when I publish a post like this... I was really used to those cohost notifs. my email is at <a href=https://wolfgirl.dev/links/>https://wolfgirl.dev/links/</a>, u know where 2 find me</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Cohost Is Dead, Long Live Cohost]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-10-01-cohost-is-dead-long-live-cohost/" />
      <id>https://wolfgirl.dev/blog/2024-10-01-cohost-is-dead-long-live-cohost/</id>
      <published>2024-10-02T02:32:34Z</published>
      <updated>2024-10-02T02:32:34Z</updated>
      <summary><![CDATA[> ## [my final chost](https://cohost.org/PolyWolf/post/7908145-my-final-chost) >  > save the world. love each other. love yourself. mis...]]></summary>
      <content type="html"><![CDATA[<blockquote><h2><a aria-hidden=true class=anchor href=#my-final-chost id=heading-my-final-chost></a><a href=https://cohost.org/PolyWolf/post/7908145-my-final-chost>my final chost</a></h2><p>save the world. love each other. love yourself. miss you, see you again, goodbye</p><p>AWOOOOOOOOOOOOOOOOOOOO</p></blockquote><p>yeah :'((((((</p><hr><p>in other news, this blog has "proper" tag support now, and my post editor works better on mobile (fixed the font size). still need to move earth on the rest of the blog styling, but that's enough for now, gnite.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Write Your Own Tools]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/" />
      <id>https://wolfgirl.dev/blog/2024-09-28-write-your-own-tools/</id>
      <published>2024-09-28T22:30:34Z</published>
      <updated>2024-09-28T22:30:34Z</updated>
      <summary><![CDATA[NOTE: this should be understood less as general-purpose advice, and more as a manifesto that applies to me personally. With that out of t...]]></summary>
      <content type="html"><![CDATA[<p>NOTE: this should be understood less as general-purpose advice, and more as a manifesto that applies to me personally. With that out of the way, let's begin:</p><hr><p>It's all to easy to, with today's software ecosystem, just use things written by other people. In some senses, this is a great success; software today is more accessible and does more things than it's ever done. However, too much of this software is controlled by big corporations without <em>your</em> interests in mind. And even for software developed by smaller teams, the pace of change is too much to keep up with. A seemingly never-ending cycle of updates always keeps you on your toes, wondering when the rug will get pulled out under your feet next.</p><p>Changing this requires changing how we interact with computers. Companies want you to view computers as tools for you to consume "content", for you to rely on for everything, for them to push advertisements, for them to collect all your data. I reject this. Computers are tools for creating things. Computers are tools for sending silly messages to your friends and hearing them laugh. You <em>can</em> make computers do anything you set your mind to, because computers are programmable, and the world-wide-web is as wide-open as it ever was.</p><p>By "Write Your Own Tools", I don't mean you should re-write the entire history of computing from scratch. It's completely fine to re-use graphics layers (HTML), programming languages (Python, Javascript), frameworks (Sanic, Astro, SolidJS), operating systems (Linux), filesystems (ext4), network stacks (TCP/IP), chipsets (x86_64), etc. (all these parentheticals are things I'm using to host my blog, after all :P). What I mean is, if there's something you want to do with a computer, and it's not already being done in a free & open manner, you can just... do it yourself. You can program! Programming is fun! It looks good on a resume too!</p><p>Even if it's already being done by someone else, writing your own tools forces you to learn, which is almost always a good thing. The end result may not be the most polished thing in the world, it might even be the jankiest, but gosh darn it, <em>you</em> made it, <em>you</em> know how to fix it, <em>you</em> can make it better. This is empowering. Hold onto this feeling, cherish it, this is what computers are actually about.</p><hr><p>Cohost's (impending) death had me re-evaluating my standing with respect to a lot of things (social media, online presence, social sphere), and this was one of the thoughts that came out & I felt strongly enough to write down. I wrote this in my own post composer, created exactly so I can publish to my blog (which you're reading now, hi!!) from my phone. I'll be reposting my favorite chosts here once I get them downloaded. Until next time!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[How should I store my blog posts?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7780963/" />
      <id>https://wolfgirl.dev/blog/7780963/</id>
      <published>2024-09-21T14:52:12Z</published>
      <updated>2024-09-21T14:52:12Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>now that Astro has their <a href=https://astro.build/blog/astro-5-beta/#stable-content-layer>Content Layer API</a> coming in their next major release, I'm suddenly a lot more flexible in how I can host the markdown files that power my blog, no longer limited to just "checked in to the Git repo". To go along with a time-honored tradition, let's make a table!</p><table class=first-is-label><thead><tr><th></th><th>Git</th><th>S3</th><th>SQLite</th><th>Third-party CMS<th></tr></thead><tbody><tr><td>Backup-ability</td><td>Very, almost required</td><td>Manually, tied to provider's CLI</td><td>Manually, w/ scp</td><td>?</td></tr><tr><td>Snapshot-ability</td><td>Very, required</td><td>Somewhat, potentially automatic, but probably not going to</td><td>Somewhat, probably manual, but probably not going to</td><td>Probably Not<td></tr><tr><td>Astro Load-ability</td><td>Easy</td><td>Potentially easy</td><td>Potentially hard</td><td>Easy if supported, potentially hard if not<td></tr><tr><td>Use My Own Post Composer</td><td>Yes</td><td>Yes</td><td>Yes</td><td>No, but maybe that's better?<td></tr><tr><td>Editable w/o Post Composer</td><td>Yes</td><td>Not Easily</td><td>Even Less Easily</td><td>Likely No<td></tr><tr><td>Ease Of Post Composer Posting</td><td>Very</td><td>Pretty</td><td>Sort Of</td><td>Very<td></tr><tr><td>Ease Of Post Composer Editing</td><td>Not Very</td><td>Pretty</td><td>Sort Of</td><td>Very<td></tr><tr><td>Deployment Solution?</td><td>Already Built</td><td>Not Built</td><td>Not Built</td><td>Not Built<td></tr><tr><td>Supports Drafts?</td><td>No</td><td>Yes</td><td>Yes</td><td>Probably<td></tr><tr><td>Also Stores Media?</td><td>Yes, but it shouldn't</td><td>Yes</td><td>No</td><td>Probably?<td></tr><tr><td>Category</td><td>Git</td><td>S3</td><td>SQLite</td><td>Third-party CMS<td></tr></tbody></table><p>yep, as expected, making a table was very helpful! it really seems like Git is still the best solution, followed by S3 as a close second. Doing more research, it seems like "Git-based CMS" is a not-uncommon thing, so really I'm pretty good where I am already.</p><p>The biggest thing I might change is moving the image files out of my Git repository. It makes clones take a lot longer than needed, right now they're actually the largest part of redeploys. I <em>think</em> having them be hosted from a S3 + Cloudflare combination & using Astro's Remote Image Functionality will help speed up build times, because that <em>should</em> be faster than pulling them thru Git. idk tho we'll see.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[OK Nevermind Lean 4 Is Kinda Bad Actually]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7534408/" />
      <id>https://wolfgirl.dev/blog/7534408/</id>
      <published>2024-09-03T12:39:21Z</published>
      <updated>2024-09-03T12:39:21Z</updated>
      <summary><![CDATA[In which I express my disappointment that the new programming language I'm learning isn't so good after all...]]></summary>
      <content type="html"><![CDATA[<p>Originally posted on Cohost. I had planned to write <a href=/tags/pwcc>pwcc</a> in Lean 4, but after these discoveries & roadblocks, I gave up on it.</p><hr><h3><a aria-hidden=true class=anchor href=#the-binary-sizes id=heading-the-binary-sizes></a>The Binary Sizes</h3><p>My "hello world but it parses command line arguments" binary was a whopping 78 megabytes, and after adding a regex library (which I haven't yet ended up using in any codepaths that can be run) it's 83 megabytes now. Talking to my friend this is because "no optimizations are done" and "all symbols from all transitive dependencies are exported" and "mathlib (the largest one & sort of the reason for Lean's existence in the first place) is usually included somewhere in that chain"</p><h3><a aria-hidden=true class=anchor href=#the-error-messages id=heading-the-error-messages></a>The Error Messages</h3><p>Because of the whole "type universe" thing it has going on, you get some very strange errors:</p><ul><li>returning the wrong type of <code>Unit</code> (there is also a universe-polymorphic <code>PUnit</code>)</li><li>using the wrong level of <code>Option.none</code> inside an <code>Option</code> monad (when the return type of the outer function and the inner function belong to different type universes)</li></ul><p>Let alone all the error messages that come up when trying to do metaprogramming stuff..</p><p>A lot of this I think is because Lean is wayyy too flexible, so errors are reported far away from where the "wrong" thing actually happened. Writing <code>Unit -> T</code> when it really wanted <code>fun () => ({}: T)</code> is not necessarily easy to debug.</p><h3><a aria-hidden=true class=anchor href=#the-metaprogramming id=heading-the-metaprogramming></a>The Metaprogramming</h3><p>Lean 4 is supposed to have the most advanced metaprogramming in existence, and to some extent that's true! However, because it's so advanced, it only operates at the AST level & lacks the capabilities to insert all the ASTs it knows about. For example, I was trying to make a custom inductive datatype. In Rust, this looks something like <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=370a6c198e6da266529a592c5916a5c1">this</a>, and translating that to Lean syntax, you'll get something like</p><pre class=syntax-highlighting><code class=language-lean4>macro t:ident "::=" (ids:ident),* : command => `(
  inductive $t where $[| $ids : $t]*
)
</code></pre><p>The problems with this you'll run into:</p><ol><li><code>macro</code> doesn't like arguments inside repeaters, you need to desugar to <code>syntax</code> + <code>macro_rules</code></li><li>the part that the <code>$[]*</code> replaces isn't a syntax category Lean recognizes</li></ol><p>Both of these generate a similar error message ("unexpected token") but (2) is the much more fundamental problem. If you try to desugar even more, you'll find that you're expected to make a syntax node of type <code>TSyntax `Lean.Parser.Command.ctor</code>. How do you get a syntax node of this type? Who knows!! If you fake it and try to case something that might look like it, you get bizarre error messages.</p><p>There's another thing you can do, which is to make an <code>elab</code> instead of a macro, but I also could not for the life of me figure out the magic incantation to make <code>Lean.Elab.Command.Declaration.inductDecl</code> stop giving me errors when I tried to <code>elabCommand</code> one with a single constructor.</p><p>In any case, I think I will go back to Rust metaprogramming, where we operate on plain-ol' token streams & the error messages make more sense to me</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Lean 4 Is A Cool Language]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7506895/" />
      <id>https://wolfgirl.dev/blog/7506895/</id>
      <published>2024-09-01T03:05:22Z</published>
      <updated>2024-09-01T03:05:22Z</updated>
      <summary><![CDATA[In which I express my positive feelings about a new programming language I'm learning!]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost. Initially, I had planned on writing <a href=/tags/pwcc/>pwcc</a> in Lean 4, and this post was part of that exploratory process.</p><hr><p>Following along with <a href=https://lean-lang.org/lean4/doc/examples/bintree.lean.html>https://lean-lang.org/lean4/doc/examples/bintree.lean.html</a>, because i Just Want To Start Writing Code and reading examples is the best way to get acquainted.</p><p>Starting off, we have just a simple polymorphic tree definition:</p><pre class=syntax-highlighting><code class=language-lean><span class=keyword>inductive</span> <span class=variable>Tree</span> <span class=punctuation>(</span><span class=variable>β</span> <span class=punctuation>:</span> <span class=variable>Type</span> <span class=variable>v</span><span class=punctuation>)</span> <span class=keyword>where</span>
  <span class=operator>|</span> <span class=variable>leaf</span>
  <span class=operator>|</span> <span class=variable>node</span> <span class=punctuation>(</span><span class=variable>left</span> <span class=punctuation>:</span> <span class=variable>Tree</span> <span class=variable>β</span><span class=punctuation>)</span> <span class=punctuation>(</span><span class=variable>key</span> <span class=punctuation>:</span> <span class=variable>Nat</span><span class=punctuation>)</span> <span class=punctuation>(</span><span class=variable>value</span> <span class=punctuation>:</span> <span class=variable>β</span><span class=punctuation>)</span> <span class=punctuation>(</span><span class=variable>right</span> <span class=punctuation>:</span> <span class=variable>Tree</span> <span class=variable>β</span><span class=punctuation>)</span>
  <span class=keyword>deriving</span> <span class=variable>Repr</span></code></pre><p>And, skipping over a few things, both a naïve <code>toList</code> implementation and a tail-recursive one:</p><pre class=syntax-highlighting><code class=language-lean><span class=keyword>def</span> <span class=variable>Tree</span><span class=operator>.</span><span class=variable>toList</span> <span class=punctuation>(</span><span class=variable>t</span> <span class=punctuation>:</span> <span class=variable>Tree</span> <span class=variable>β</span><span class=punctuation>)</span> <span class=punctuation>:</span> <span class=variable>List</span> <span class=punctuation>(</span><span class=variable>Nat</span> × <span class=variable>β</span><span class=punctuation>)</span> <span class=operator>:=</span>
  <span class=keyword>match</span> <span class=variable>t</span> <span class=keyword>with</span>
  <span class=operator>|</span> <span class=variable>leaf</span> <span class=operator>=></span> <span class=punctuation>[]</span>
  <span class=operator>|</span> <span class=variable>node</span> <span class=variable>l</span> <span class=variable>k</span> <span class=variable>v</span> <span class=variable>r</span> <span class=operator>=></span> <span class=variable>l</span><span class=operator>.</span><span class=variable>toList</span> ++ <span class=punctuation>[(</span><span class=variable>k</span><span class=punctuation>,</span> <span class=variable>v</span><span class=punctuation>)]</span> ++ <span class=variable>r</span><span class=operator>.</span><span class=variable>toList</span>

<span class=keyword>def</span> <span class=variable>Tree</span><span class=operator>.</span><span class=variable>toListTR</span> <span class=punctuation>(</span><span class=variable>t</span> <span class=punctuation>:</span> <span class=variable>Tree</span> <span class=variable>β</span><span class=punctuation>)</span> <span class=punctuation>:</span> <span class=variable>List</span> <span class=punctuation>(</span><span class=variable>Nat</span> × <span class=variable>β</span><span class=punctuation>)</span> <span class=operator>:=</span>
  <span class=variable>go</span> <span class=variable>t</span> <span class=punctuation>[]</span>
<span class=variable>where</span>
  <span class=variable>go</span> <span class=punctuation>(</span><span class=variable>t</span> <span class=punctuation>:</span> <span class=variable>Tree</span> <span class=variable>β</span><span class=punctuation>)</span> <span class=punctuation>(</span><span class=variable>acc</span> <span class=punctuation>:</span> <span class=variable>List</span> <span class=punctuation>(</span><span class=variable>Nat</span> × <span class=variable>β</span><span class=punctuation>))</span> <span class=punctuation>:</span> <span class=variable>List</span> <span class=punctuation>(</span><span class=variable>Nat</span> × <span class=variable>β</span><span class=punctuation>)</span> <span class=operator>:=</span>
    <span class=keyword>match</span> <span class=variable>t</span> <span class=keyword>with</span>
    <span class=operator>|</span> <span class=variable>leaf</span> <span class=operator>=></span> <span class=variable>acc</span>
    <span class=operator>|</span> <span class=variable>node</span> <span class=variable>l</span> <span class=variable>k</span> <span class=variable>v</span> <span class=variable>r</span> <span class=operator>=></span> <span class=variable>go</span> <span class=variable>l</span> <span class=punctuation>((</span><span class=variable>k</span><span class=punctuation>,</span> <span class=variable>v</span><span class=punctuation>)</span> :: <span class=variable>go</span> <span class=variable>r</span> <span class=variable>acc</span><span class=punctuation>)</span></code></pre><p>Neat! Good to see that a functional programming language can in fact do functional programming things. Wait, what's this next paragraph:</p><blockquote>We now prove that <code>t.toList</code> and <code>t.toListTR</code> return the same list. The proof is on induction, and as we used the auxiliary function <code>go</code> to define <code>Tree.toListTR</code>, we use the auxiliary theorem <code>go</code> to prove the theorem.</blockquote><p>oh...</p><pre class=syntax-highlighting><code class=language-lean><span class=keyword>theorem</span> <span class=variable>Tree</span><span class=operator>.</span><span class=variable>toList_eq_toListTR</span> <span class=punctuation>(</span><span class=variable>t</span> <span class=punctuation>:</span> <span class=variable>Tree</span> <span class=variable>β</span><span class=punctuation>)</span>
        <span class=punctuation>:</span> <span class=variable>t</span><span class=operator>.</span><span class=variable>toList</span> <span class=operator>=</span> <span class=variable>t</span><span class=operator>.</span><span class=variable>toListTR</span> <span class=operator>:=</span> <span class=function>by
  simp </span><span class=punctuation>[</span><span class=variable>toListTR</span><span class=punctuation>,</span><span class=function> </span><span class=variable>go</span><span class=function> </span><span class=variable>t</span><span class=function> </span><span class=punctuation>[]]</span><span class=function>
</span><span class=variable>where</span><span class=function>
  </span><span class=variable>go</span><span class=function> </span><span class=punctuation>(</span><span class=variable>t</span><span class=function> </span><span class=punctuation>:</span><span class=function> </span><span class=variable>Tree</span><span class=function> </span><span class=variable>β</span><span class=punctuation>)</span><span class=function> </span><span class=punctuation>(</span><span class=variable>acc</span><span class=function> </span><span class=punctuation>:</span><span class=function> </span><span class=variable>List</span><span class=function> </span><span class=punctuation>(</span><span class=variable>Nat</span><span class=function> × </span><span class=variable>β</span><span class=punctuation>))</span><span class=function>
     </span><span class=punctuation>:</span><span class=function> </span><span class=variable>toListTR</span><span class=operator>.</span><span class=variable>go</span><span class=function> </span><span class=variable>t</span><span class=function> </span><span class=variable>acc</span> <span class=operator>=</span> <span class=variable>t</span><span class=operator>.</span><span class=variable>toList</span> ++ <span class=variable>acc</span> <span class=operator>:=</span> <span class=function>by
    </span><span class=variable>induction</span> <span class=variable>t</span> <span class=variable>generalizing</span> <span class=variable>acc</span> <span class=operator>&lt;</span><span class=punctuation>;</span><span class=operator>></span>
      <span class=variable>simp</span> <span class=punctuation>[</span><span class=variable>toListTR</span><span class=operator>.</span><span class=variable>go</span><span class=punctuation>,</span> <span class=variable>toList</span><span class=punctuation>,</span> <span class=operator>*</span><span class=punctuation>,</span> <span class=variable>List</span><span class=operator>.</span><span class=variable>append_assoc</span><span class=punctuation>]</span></code></pre><blockquote>The <code>[csimp]</code> annotation instructs the Lean code generator to replace any <code>Tree.toList</code> with <code>Tree.toListTR</code> when generating code.</blockquote><p>oh???</p><pre class=syntax-highlighting><code class=language-lean><span class=attribute>@[</span><span class=variable>csimp</span><span class=punctuation>]</span> <span class=keyword>theorem</span> <span class=variable>Tree</span><span class=operator>.</span><span class=variable>toList_eq_toListTR_csimp</span>
                 <span class=punctuation>:</span> @<span class=variable>Tree</span><span class=operator>.</span><span class=variable>toList</span> <span class=operator>=</span> @<span class=variable>Tree</span><span class=operator>.</span><span class=variable>toListTR</span> <span class=operator>:=</span> <span class=function>by
  </span><span class=variable>funext</span><span class=function> </span><span class=variable>β</span><span class=function> </span><span class=variable>t</span><span class=function>
  </span><span class=function>apply </span><span class=variable>toList_eq_toListTR</span></code></pre><p>Like i knew that this was the "functional programming used for proving things" language, but such a tight integration w/ the compiler itself is very cool. Formal verification ftw!</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: Dead Dead Demon's Dededede Destruction]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7444639/" />
      <id>https://wolfgirl.dev/blog/7444639/</id>
      <published>2024-08-27T01:18:02Z</published>
      <updated>2024-08-27T01:18:02Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p><img alt="chapter cover featuring much of the main cast" src=https://static.wolfgirl.dev/5d1c7161-90d0-4ecc-a9d4-6aee2e2c744b/IMG_0185.jpeg></p><p>another good manga by Inio Asano. follows a group of girls from middle school onwards as they navigate an ongoing alien invasion in Tokyo. i finished it today, really liked it up until the last volume, at which point it sorta became a parody of itself, pretty much solely in order to end at exactly chapter 100. i would still recommend it tho! lots of interesting ideas it presents and explores. there's even a trans girl in that image and you didn't realize</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[ Choose Your Own Adventure]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7283112/" />
      <id>https://wolfgirl.dev/blog/7283112/</id>
      <published>2024-08-13T19:41:26Z</published>
      <updated>2024-08-13T19:41:26Z</updated>
      <summary><![CDATA[I did a simple, spontaneous choose-your-own-adventure game with friends on Cohost! They sent in asks and I responded.]]></summary>
      <content type="html"><![CDATA[<p>The following are a series of short posts, each done in response to viewer "asks" (presented in blockquotes for context). People seemed to like it, my inbox had many such asks, but eventually I grew tired of the one-sided game and stopped responding as quickly, which killed it. Oh well! At least I had fun, and hope that shines thru here :)</p><p>The publish date on this post is the publish date of the first response. The last response is a fair bit removed.</p><hr><blockquote><p>wolf on the</p></blockquote><p><em>stumbles about in fit of surprise/panic at receiving an Ask for the first time in a while</em></p><p><em>hurriedly shufs all the papers on her desk onto the floor in order to look presentable while presenting her Answer</em></p><p><em>clears her throat and opens her mouth to speak, staring intently at a point just past the viewer (is it a teleprompter?)</em></p><h2><a aria-hidden=true class=anchor href=#breakfast-nook id=heading-breakfast-nook></a>“Breakfast Nook”<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup></h2><p><em>deep sigh of relief, she didn't mess up for once</em></p><p><em>realizes she's been on camera for too long and dives back under her desk, which doesn't have a front panel so you can still see her just sprawled out there. oh look she's reading manga now</em></p><hr><blockquote><p>do you think that is acceptable wolf-like behaviour?</p></blockquote><p><em>slowly peeks back over the desk, realizes she's been visible the entire time, and blushes</em></p><p><em>composes herself & assumes a business-like posture, turning to her left to look outside, before very intently turning back toward the viewer</em></p><h2><a aria-hidden=true class=anchor href=#yes-w id=heading-yes-w></a>“Yes (=w=)”</h2><p><em>slumps back down in her chair to rest her chin on the desk, and then naps like this</em></p><hr><blockquote><p>Purple! 💜</p></blockquote><p><em>wakes up from a long nap and yawns, slightly unsure of what she just heard. double-checking with external sources confirms that yes, this Ask is exactly what she thought it was.</em></p><p><em>now she's fully awake and even more confused. how is she supposed to answer this? is this Jeopardy or something? she can feel the tension in the air rising. Something is about to Happen. giving it her best shot, she answers the answer with a question:</em></p><h2>“Purple? <span style=width:.5em;display:inline-flex;overflow:hidden>💜</span>–”</h2><p><em>she instinctively covers her mouth to prevent the last syllable(?) from leaking out. but it's too late. the remaining impossible noise is screaming inside her mouth to be reunited with that already permeating the room. there's no choice; she gives in</em></p><h2>“–<span style=width:.5em;display:inline-flex;overflow:hidden;transform:scaleX(-1)>💜</span>”</h2><p><em>it Happens. a floating Purple heart materializes in the center of the room and within a fraction of a second glows so blindingly Purple you could see it thru your eyelids. with a soundless BANG it leaves only Purple behind. everything is Purple now. the floors, wall, ceiling, desk, chair, her, all the same shade of Purple. the air is Purple. the atoms are Purple. gravity is Purple. Purple. Purple.</em></p><p><em>she sighs and resets the room, stepping out to take a shower to wash all the purple off.</em></p><hr><blockquote><p>meow~?</p></blockquote><p><em>a kitty! cats aren't usually this friendly around her, so she takes the opportunity to kneel down and gently pet it, scritching under its chin. the cat purrs, and she purrs back:</em></p><h2><a aria-hidden=true class=anchor href=#good-girl id=heading-good-girl></a>“Good girl~”</h2><hr><blockquote><p>squeak</p></blockquote><p><em>a vaguely mouse-like indistinct animal noise emanates from across the room, and she turns her head to scan for what caused it while still petting the cat that walked in earlier. oh! it's a second cat! today must be her lucky day. she motions with her free hand and beckons:</em></p><h2><a aria-hidden=true class=anchor href=#pspspspspsps-cmere-kitty id=heading-pspspspspsps-cmere-kitty></a>“Pspspspspsps, c'mere kitty~”</h2><p><em>the second cat obliges, happily trotting over & nuzzling her hand. joy! she's now petting two cats at once. suddenly she realizes how untenable her situation has become. What if something demands the use of her hands and she has to stop petting the cats? Or worse, what if a third cat comes in and demands pets too? That would be so unfair. Oh well, better savor the good times while they're here. She smiles serenely and continues lovingly petting both cats.</em></p><hr><blockquote><p>💥</p></blockquote><p><em>she eyes the latest message arrive on the screen in front of her, realizes what it's intended to do, and has a brief jolt of fear. a purposeful attack on the medium in which she exists, as demonstrated by what happened last time? who would do such a thing??</em></p><p><em>she waits, frozen, but nothing happens. then she remembers: she's already installed defenses for a situation like this! and they must have worked, considering the previously-petted cats sleeping next to her haven't woken up from any noise</em></p><p><em>suddenly, as if the universe deigned to play a trick on her just as she let her guard down, a small 💥 gets thru, not enough to cause any damage but more than enough to wake, startle, & scatter both sleeping kitties. shock turns to sullenness and she sulks, not saying anything.</em></p><hr><blockquote><p>awoo?</p></blockquote><p><em>a small noise causes her ears to twitch in her sleep, slowly rising her from her slumber. she blearily fumbles about, which causes the computer mouse in her hand to clatter onto the floor. jolting awake, she's greeted with... 117 unread emails?? ok phew thank goodness it hasn't gone over 200 yet.</em></p><p><em>she rewinds the security footage a bit to find out what that noise was, leans back in her chair for a moment to contemplate an appropriate response, but who is she kidding there's only one possible answer:</em></p><h2><a aria-hidden=true class=anchor href=#awooghk-rmm-rghaack-awooooooooooooooo id=heading-awooghk-rmm-rghaack-awooooooooooooooo></a>“Awooghk... rmm. RGHAACK. Awooooooooooooooo!!”</h2><p><em>she severely misjudged the state of her morning throat in front of her entire audience, how embarrassing... oh well, no matter! she leans back in her chair, happy to start off the weekend right with a wolf howl.</em></p><section class=footnotes data-footnotes><ol><li id=fn-1><p>https://youtu.be/x5zFnzwwveg <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: Milk outside a bag of milk outside a bag of milk]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7235326/" />
      <id>https://wolfgirl.dev/blog/7235326/</id>
      <published>2024-08-10T00:49:05Z</published>
      <updated>2024-08-10T00:49:05Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><h2><a aria-hidden=true class=anchor href=#milk-outside-a-bag-of-milk-outside-a-bag-of-milk id=heading-milk-outside-a-bag-of-milk-outside-a-bag-of-milk></a><a href=https://store.steampowered.com/app/1604000>Milk outside a bag of milk outside a bag of milk</a></h2><p>Also very good. Art is at a much higher level, soundtrack and colors maintain the original unique vibe. Took me ~1hr to get thru my first playthru, there are apparently 5 total endings possible (judging by Steam achievements). Still very well worth the pricetag.</p><p>Playing <a href=/blog/7195563/>the first game</a> is essential to understand this one imo. The opening cutscene and the ending I got simply don't make sense without it. Do it go play them now don't procrastinate like i did</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: Milk inside a bag of milk inside a bag of milk]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/7195563/" />
      <id>https://wolfgirl.dev/blog/7195563/</id>
      <published>2024-08-07T01:36:54Z</published>
      <updated>2024-08-07T01:36:54Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><h2><a aria-hidden=true class=anchor href=#i-finally-played-milk-inside-a-bag-of-milk-inside-a-bag-of-milk id=heading-i-finally-played-milk-inside-a-bag-of-milk-inside-a-bag-of-milk></a>I finally played <a href=https://store.steampowered.com/app/1392820>Milk inside a bag of milk inside a bag of milk</a>.</h2><p>Great visual novel. The vibes are simply immaculate. Short game (~20min) but easily worth the (very cheap!) price of admission. idk why I procrastinated playing it for so long.</p><p>Will be interesting to see how the sequel, <a href=https://store.steampowered.com/app/1604000>Milk outside a bag of milk outside a bag of milk</a>, holds up in comparison.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: Blue Flag]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/6884663/" />
      <id>https://wolfgirl.dev/blog/6884663/</id>
      <published>2024-07-22T00:50:39Z</published>
      <updated>2024-07-22T00:50:39Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><h2><a aria-hidden=true class=anchor href=#please-read-blue-flag id=heading-please-read-blue-flag></a>Please Read <em><a href=https://www.viz.com/blue-flag>Blue Flag</a></em>.</h2><p>it's tagline is "An unexpected love quadrangle with a dash of unrequited love." and hoo boy they were not kidding about the love quadrangle:</p><p><img alt src=https://static.wolfgirl.dev/06b6e089-ad3a-4d34-a9c9-3217d524e2e5/blue%20flag%20love%20quadrangle.png></p><p>that's right! we have a whopping <em>3/4</em> characters being some form of queer.</p><details><summary>Diagram alt text & extra info:</summary> <ul><li>Taichi (dark haired guy, small): <ul><li>-> Toma: childhood friends</li><li>-> Futaba: in love</li><li>-> Masumi: acquaintance. thinks she's mean to him</li><li>Toma -> Big Girl: thinks he has a crush</li><li>Futaba -> Toma: thinks she has a crush, so doesn't act on his own crush</li></ul></li><li>Toma (light haired guy, big & strong): <ul><li>-> Taichi: in love (Gay!)</li><li>-> Futaba: good friend</li><li>-> Masumi: good friend, knows about her crush on Futaba</li><li>Taichi &lt;-> Futaba: knows about their mutual attraction</li></ul></li><li>Futaba (light haired girl, small): <ul><li>-> Taichi: "likes", but actually in love with</li><li>-> Toma: wants to be (in a Trans way!), but doesn't realize this and thinks it's love <ul><li>I'm still using she/her pronouns for her since she's clearly an eggy trans, never outright confirmed, only hinted at quite a lot</li></ul></li><li>-> Masumi: good friends</li></ul></li><li>Masumi (dark haired girl, aloof): <ul><li>-> Taichi: acquaintance. thinks he needs to man up about his feelings more.</li><li>-> Toma: good friends, knows about his crush on Toma</li><li>-> Futaba: in love (Lesbian!)</li></ul></li></ul></details><p>Some notes about this diagram:</p><ol><li>It contains spoilers, but also I believe they're the kind of spoilers that enhance the manga; the start is pretty slow and having this knowledge going in makes it better</li><li>These relations do shift a bit as time goes on, but this is sort of the baseline for how the drama works.</li><li>One could add more characters to this diagram, but this is the core of the quadrangle upon which most of the drama is built.</li></ol><p>anyways yeah it's really really good & only 54 chapters (8 volumes), please give it a shot if u like coming-of-age manga that grapple with queerness.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: Goodnight, Punpun]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/6936628/" />
      <id>https://wolfgirl.dev/blog/6936628/</id>
      <published>2024-07-18T19:53:41Z</published>
      <updated>2024-07-18T19:53:41Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost, across 2 posts.</p><hr><p>wow turns out i am a sucker for unconventional artsy stories dealing with the human psyche. I did love <a href=/blog/5931968/>Shimeji Simulation</a>, after all.</p><p>this one is a lot heavier though. unashamed to portray adults as fundamentally broken creatures, and children as just one step away from being broken by adulthood or already broken by a traumatic upbringing<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>. let alone any of the sex/violence stuff.</p><p>anyways! i plan on continuing with it. likely won't end up as a favorite because it makes me feel sad & confused instead of happy, but likely worth it nonetheless</p><hr><p>what a ride. glad there was something approximating a happy ending, at least. I started reading it quite some time ago and have been slowly digesting all 7 volumes (13 parts?) as I could stomach. There were parts I didn't get because I don't have enough life experience and parts that made me hate everyone in it and parts that I'm grateful I'll never understand, but that's just life I guess. Big and messy and impossible to summarize without having lived thru it. I recommend this manga if you can stand being reminded of the human condition.</p><p>the art's pretty nice too ya</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>that was really sad to type out wow <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf's Style Guide]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/6372078/" />
      <id>https://wolfgirl.dev/blog/6372078/</id>
      <published>2024-06-10T22:51:20Z</published>
      <updated>2024-06-10T22:51:20Z</updated>
      <summary><![CDATA[In which I lay down some ground rules for how I write & use punctuation.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost. It did not get a lot of traction, but it turned out to be quite important for me, since writing down these rules has compelled me to follow them ever since, for better or worse.</p><hr><ul><li>The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY", when in all-caps, are to be interpreted as described in <a href=https://www.rfc-editor.org/rfc/rfc2119>RFC 2119</a>, both in this document and in all documents written to this style guide.</li><li>punctuation SHOULD go outside the boundaries of a link's text.</li><li>in general, commas are for pauses in speech, so no hard & fast rule on the oxford comma, though it is preferred. you should consider breaking up a sentence if it becomes too run-on, however.</li><li>groupings of nouns SHOULD use "&", and concatenation of sentences SHOULD use "and".</li><li>if a grouping is joined by "/", it is as if all options are being read in parallel.</li><li>in any grouping, each option MUST be grammatically correct if read alone.</li><li>the spelling changes though -> tho & through -> thru MUST be used. i am pushing these thru by force.</li><li>"thorough" is not a real word and SHOULD NOT be used.</li><li>quotation marks MUST contain only the punctuation of the quoted sentence; no more, no less.</li><li>"" (non-curly quotes) are the only acceptable quotation marks; other options like «» (french), „“ (german), or “” (curly) MUST NOT be used, unless explicitly present in quoted text.</li><li>similarly, ' (non-curly apostrophe) is the only acceptable apostrophe.</li><li>if the source of some word comes directly from code, you SHOULD surround it in `backticks`, even if there is no markdown renderer.</li><li>capital letters for Proper Nouns à la old(er) english, for Technical Terminology, or Just For Emphasis are all valid. if writing in paragraphs of text, one MAY capitalize the first letter of each sentence to improve readability.</li><li>the use of semicolons is encouraged! semicolons separate sentences that are related enough for a period to seem a bit awkward, but not so related that a comma would work better; this is why the semicolon is comprised of both a period and a comma, you see.</li><li>"and/or" MUST NOT be used. "or" as a joiner is always inclusive, unless the list is preceded by "either".</li><li>latin-isms like "e.g.", "i.e.", and "ex." SHOULD be avoided, because I cannot tell the difference between them. "etc." is the only acceptable one, and it MUST appear in shortened form, always including the period.</li><li>hyphens SHOULD be used to indicate you are: <ul><li>making up a word on-the-spot (see above).</li><li>using some pre-existing phrase (see above).</li><li>to avoid umlauts (see above).</li></ul></li><li>parentheticals MAY be full sentences. if a paranthetical is a full sentence, the parenthesis MUST enclose the sentence exactly, including ending punctuation. otherwise, the parenthesis SHOULD come before the ending punctuation of the containing sentence. (this rule clearly delineates the two types of parentheticals.)</li><li>y'all MUST use "y'all" for 2nd person plural.</li><li>possessives' apostrophes MUST have the apostrophe after the s when plural, where applicable.</li><li>if the source of some word has diacritical marks, you MUST keep them (see blåhaj).</li><li>use _underscores_ for emphasis, and *stars* for action descriptions, even if there is no markdown renderer, and even if markdown itself just makes both into italics.</li><li>acceptable emoji outside of short-form texts (use sparingly): 😤😔 <ul><li>on co host dot org, all eggbug emoji are also acceptable.</li></ul></li><li>emoticons are more often accepted. :) & :P are most common.</li><li>always refer to Cohost as co host dot org.</li><li>remember to have fun!</li></ul>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: Shimeji Simulation]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/5931968/" />
      <id>https://wolfgirl.dev/blog/5931968/</id>
      <published>2024-05-11T22:12:18Z</published>
      <updated>2024-05-11T22:12:18Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>i read the first 4 volumes in a day, then had to pace myself with the last couple because i didn't want to waste a drop of goodness.</p><p>it has the best possible ending. everything was leading up to that moment. i'm so glad i was alive to experience it ;-;</p><p>in the running for best manga i've ever read, 10/10 no notes (that i can give without spoilers)</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Some Reasons Your React App Might Be Slow]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/5801824/" />
      <id>https://wolfgirl.dev/blog/5801824/</id>
      <published>2024-05-02T01:29:01Z</published>
      <updated>2024-05-02T01:29:01Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost in 2 parts.</p><hr><p><a href=https://mastodon.social/@danluu/112368308357914085>Dan Luu, on Mastodon</a>:</p><blockquote><p>Naive question: why do React apps in the real world tend to be slow?</p><p>I tried doing a React tutorial and the result was quite fast (w.r.t. latency & CPU utilization on low-end devices) until the tutorial has you replace "manual" / "low-level" react calls with commonly used libraries, e.g., using TanStack Query instead of useEffect plus a manually instantiated cache.</p><p>Is the main issue that libraries tend to be big and slow or is there another major cause of React app slowness?</p></blockquote><p>wow, a question i actually have a smidgen of expertise on!</p><p>At work, we have a very big React app. Here are some things which were causing slowness, which we know for certain because fixing them improved performance:</p><ol><li>Excessive use of react-redux and redux-saga. These are libraries which have you put everything you want to share between unrelated (not direct children) components in global state, and then runs "selectors" (arbitrary javascript functions to get specific data out of the global state) every time any of that state changes. Across thousands of selectors and many state changes per second, this really adds up. We have good reason to believe we're one of the largest users of Redux, so this might not be an issue for everyone.</li><li>Excessive rerenders, and non-memoized rerenders. You can have excessive rerenders when your component gets non-memoized objects (or, functions, arrays, etc.) as props. A child can do all the proper useCallback and useMemo it wants, but it's on the parent to not mess up. Which happens often since it's a pain to wrap every single arrow function in useCallback everywhere (React docs explicitly say to not do this until performance is a concern, even!). And even then, if a parent rerenders for whatever reason, it will rerender all its children, even if their props are all the same, unless the child is memoized, which is its own problem if the heirarchy is very deep.</li><li>Too Many DOM nodes. This is a general problem with component-like frameworks, encouraging a kind of abstraction leading to far too many wrapper divs since they're so easy to add. All these extra DOM nodes come at a surprising performance penalty, especially in browsers like Safari.</li></ol><p>Not quite an exhaustive "things that make your React app slow", but hopefully useful nonetheless :P</p><hr><h1><a aria-hidden=true class=anchor href=#other-reasons-your-very-big-website-might-be-very-slow id=heading-other-reasons-your-very-big-website-might-be-very-slow></a>Other Reasons Your Very Big Website Might Be Very Slow:</h1><ol start=4><li>Animations. The designer loves to go ham and make absolutely everything animated, but eventually yeah those start to add up too, especially when animating massive DOM trees and causing big layout shifts. Combined with (3) above, it sometimes takes a solid <strong>2 seconds</strong> to open a certain problematic side panel, on a good machine. Good luck on a little dinky machine. This problem is also exacerbated by using libraries like framer-motion, which have lots of cool animation curves, not all of them implemented efficiently! Using CSS animations where possible also lets you quickly turn them off with <code>animation: none !important; transition: none !important;</code></li><li>Opacity. The designer also loves using <code>rgba(255, 255, 255, 0.05)</code> for component backgrounds, because it layers nicely over almost any other background. Unfortunately, they require more work by the browser to render properly, which can amount to a surprising drop in performance on weaker clients. Especially when combined with animated transparencies🙀 Making an option to change to all flat colors was a noticeable gain. And this is all to say nothing of blur, which tanks performance even harder (we've almost got rid of it entirely now)</li><li>Excessive Repaints From Weird Layouts. Still haven't quite gotten to the bottom of this so can't say for sure, but it seems that certain layout things (CSS grid? Weird stacking contexts?) cause browsers to think they need to repaint more than what's actually changed, leading to a drop in performance. Saying "hey change the size of this one thing slightly" makes it go "oh i should repaint the whole screen, got it", even when there's no actual layout shift. This is once again exacerbated by animations, which cause this many times a second.</li></ol><p>Once again, these are just problems we've identified, and there may be more, I am by no means a Web Performance Expert. yet.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: 长歌行, a.k.a. Chang Ge Xing, a.k.a. The Long Ballad, a.k.a Song Of The Long March]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/5663964/" />
      <id>https://wolfgirl.dev/blog/5663964/</id>
      <published>2024-04-27T00:54:42Z</published>
      <updated>2024-04-27T00:54:42Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost. It got a lot less attention than I had hoped for given the amount of time I put into it, and looking back on it yeah I can kinda see why.</p><hr><p>A while back, I swapped manga recommendations with a friend, which led to me reading <a href=https://mangafire.to/read/song-of-the-long-march.56xv/en/chapter-1>Song Of The Long March</a>. My friend has good taste, so naturally it was really really good. More recently, while thinking of things to do on a lazy weekend, we stumbled across <a href="https://www.youtube.com/playlist?list=PLuidrAcAGAOOr5MAnZIaUvknOdruRHs93">The Long Ballad</a>, a live-action adaptation of the manga, available for free on YouTube, and decided to give it a watch. It was not very good at all. Bad, even.</p><p>In this essay, I'll try to explain why I think the manga is so much better than the show. Strap yourself in, it's gonna be a long one. Spoilers ahead.</p><hr><h2><a aria-hidden=true class=anchor href=#first-episodes id=heading-first-episodes></a>First Episodes</h2><p>I believe that, from just seeing the first episode/chapter of a piece of media, one can generally sense the upper bound of quality it's likely to achieve. The first episode is where authors should be pulling out all the stops, grabbing your attention with their juciest hooks, setting the scene for what you should expect going forward. So, what do these first episodes look like?</p><h3><a aria-hidden=true class=anchor href=#song-of-the-long-march id=heading-song-of-the-long-march></a>Song Of The Long March</h3><p>Princess Li Chang Ge throws off her pursuers by commandeering the horse pulling her carriage, galloping full-tilt toward a cliff edge, stabbing it to make it jump, and then jumping off the horse mid-air to gracefully make an otherwise impossible leap, successfully escaping. Title card.<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>1</a></sup></p><p><img alt="Li Chang Ge holding a bloody dagger, her princess dress billowing in the wind" title="she's so cool" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420224735.png></p><p>Her pursuers return to the capitol in low spirits, the sole failures of the new dynasty's attempt to squash out the old royal family, but are covered up for by a crafty retainer who is sympathetic to the princess, thinking she's powerless and safe to let go with her life intact.</p><p>Meanwhile, Li Chang Ge disguises herself as a traveling boy, exchanges most of her jewelry she had on her for funds, swiftly dispatches some highway robbers who try to take said funds, and then tricks some guards into eating poisoned dim-sim, all so she can visit the grave of her mother.</p><p><img alt="Li Chang Ge standing over the bodies of poisoned guards" title="what a badass" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420225305.png></p><p>There, she meets her mentor, who has also helped cover up her continued survival, and who predicted she would arrive at this gravesite. They trade words over what she'll do next, and she swears to enact her revenge on the current emperor who's killed her family:</p><p><img alt="[Li Chang Ge, looking crazed and manic, snapping a hairpin with her bare hands]: Till the bitter end" title="hm! not quite mentally stable atm but we stan regardless" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420224512.png></p><p>Chapter ends.</p><p>So yeah. She's hardcore, she's a girlboss, she's intelligent & coolheaded when she needs to be, and she's traumatized. Overall, a really interesting character that you can't help wonder what she'll do next.</p><h3><a aria-hidden=true class=anchor href=#the-long-ballad id=heading-the-long-ballad></a>The Long Ballad</h3><p>Li Chang Ge is fleeing her pursuers on horseback, galloping onto a rope bridge when the boards snap out from underneath her, sending her plunging into the raging river below.</p><p><img title="the cgi for this wasn't great and they also used some fake manga panels afterwards as she was floundering in the water?? the fake manga panels never show up again" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420230059.png></p><p>huh. that's different...</p><p>Cut to some indeterminate amount of time in the past, and Li Chang Ge berates a guard for almost trampling over some dude. No one is in the right here<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>.</p><p><img alt="[Li Chang Ge]: Is that how the guard in Mansion of Crown Prince behave?" title="cmon girl you attracted a crowd, they're all looking at u" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420230307.png></p><p>Next, Li Chang Ge goes to see her mother, whom she has a loving and intimate relationship with<sup class=footnote-ref><a data-footnote-ref href=#fn-4 id=fnref-4>3</a></sup>, and they talk about some marriage contract thingy.</p><p><img alt="[Li Chang Ge]: You're so sweet" title="aww so sweet" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420230543.png></p><p>After that, Li Chang Ge goes to hang out with her cousin, and they exchange lucky charms and talk about the cousin's crush. They also witness a (secret?) execution (by accident?), which does not have much impact other than to cause the cousin to lose her charm thingy on the way to the soccer game the crush is playing in.</p><p><img alt="[Cousin]: This is the amulet" title="just some normal girls, doing normal girl things" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420230818.png></p><p>At the soccer game, the crush gets hurt, and Li Chang Ge sneakily steals his uniform while he's in the sick bay, walks onto the field wearing a batman-style mask, and rallies the team to win the match with a critical tie-breaking final point. The other team might have won if their leader (whom the audience knows is an important person because they have an hawk screech sound effect every time he appears on screen) didn't get out of position to save a child on the sidelines from a ball speeding out-of-bounds.</p><p><img alt="Li Chang Ge doing an epic backflip bicycle kick to score the winning goal" title="yes this is just Chinese Quidditch, no i have no idea if it's an actual sport or not and i am too lazy to look it up" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420231325.png></p><p>As a reward for being MVP in the soccer game, she gets a cool dagger as a prize, which she then gives to her mentor as thanks for teaching her all these years. They then do a mock fight with the extremely dangerous unsheathed dagger.</p><p><img alt="[Li Chang Ge, to Mentor]: You taught me martial arts and warcraft" title="what a thoughtful gift! i'm sure he won't use it to murder your father, the emperor next episode" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420231555.png></p><p>The episode ends.</p><p>????? No seriously, what the heck was <em>any</em> of that??</p><h3><a aria-hidden=true class=anchor href=#analysis id=heading-analysis></a>Analysis</h3><p><img alt="'Kowalski, Analysis' from The Penguins Of Madagascar animated series" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240420231826.png></p><p>As you can see, those were <em>very</em> different first episodes. The TV show completely invents an entire storyline, that, in my view, does a much worse job of introducing the main character. The first chapter of the manga is focused & punchy, quickly getting the ball rolling and setting out all the who, what, where, when, why, & how. The TV show's first episode is unfocused & plodding, overall disjointed with how it presents the characters and sets up the story.</p><p>To be fair to TV show, there may be lots of symbolism more familiar to Chinese audiences that I'm failing to pick up on. But also, the source material was right there!! Why did it deviate so much?? I have a couple hypotheses for why the producers would have done this, neither of them charitable:</p><ol><li>They needed to pad the runtime for their planned 49 episode run, each 40min long</li><li>They didn't want to lead with such dark themes because that's what expected in their target audience of drama-watchers.</li></ol><p>Overall, not a great impression. But it wasn't just the plot that turned me off; there are more surface-level problems too.</p><h2><a aria-hidden=true class=anchor href=#aesthetics id=heading-aesthetics></a>Aesthetics</h2><p>It's a bit unfair to compare to compare the manga and the show directly, since they're different forms of media, but I do think comparisons within each medium is warranted.</p><h3><a aria-hidden=true class=anchor href=#song-of-the-long-march-1 id=heading-song-of-the-long-march-1></a>Song Of The Long March</h3><p>As you can see from the panels shown previously, this manga is just absolutely gorgeous. The linework, shading, composition, paneling<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>4</a></sup>, scenery, character design, portraits, you name it, it's spectacular. Probably the thing I would guess hooks most readers, who are then also captivated enough by the story that they can't put it down. The art also helped get me through the rare slow parts, being able to appreciate every panel.</p><h3><a aria-hidden=true class=anchor href=#the-long-ballad-1 id=heading-the-long-ballad-1></a>The Long Ballad</h3><p>By comparison, the direction here is extremely mediocre. Looks exactly like what you'd expect a mid-budget drama to look like. As you can see from the screenshots, the costumes are ok, the scenery is ok, the composition is ok, everything is just barely passable and inoffensive enough to not stand out. Good for a show you just want to make money off of, not good enough for a Piece Of Art like the manga was.</p><p>I should mention, and this is one of the reasons I wanted to write this piece, the editing is <em>very strange</em>. Which is the least offensive way I can put it. The only way I can truly explain it is by showing off a shot-by-shot breakdown of the <em>very second scene</em>, which had me crying laughing by the time it was over. Not because the scene itself was supposed to be funny, but because the editing was just that bad.</p><p>Ok so: here we go:</p><p><img title="pretty sure most of those buildings are CGI. it's actually pretty solid tho!" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421163410.png></p><p>Establishing shot of some city. Cool.</p><p><img title="me when im brooding" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421163508.png></p><p>Slow pan and rotate around from behind this dude to frame his face looking out the window, as ominous music plays.</p><p><img alt="[Attendant]: Changan is indeed a lively city" title="nice hotel room. where's the bed tho??" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421163616.png></p><p>We see the room he's staying in and the attendant behind him, and they're both POSTED UP.</p><p><img alt="[Dude]: What you see is not necessarily the truth." title="very natural overlooking position" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421163745.png></p><p>Cut to a different angle from outside the balcony, framing both him and his attendant. These screenshots don't do it justice since it's static, but neither of them move an inch as they speak. It's frankly incredible to witness.</p><p><img alt="[Dude]: The prosperity for now may not last long." title="brooding continues" src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421163839.png></p><p>Quick cut to an angle just above his brow to highlight the end of his sentence. Take note that it's close-up with low depth-of-field. They <em>love</em> this shot.</p><p>Cut to the same static shot of the room as before to see them walk out the left side, as stiffly as possible, with an ominous flute playing to punctuate their exit.</p><p><img title="me & my homie, in da beeg city" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421164137.png></p><p>Cut to a straight-on shot of them exiting the building, STILL POSTED UP, when suddenly a <strong>slow-motion effect hits.</strong> But not like, <em>that</em> slow, maybe 3/4 of real-time speed?</p><p><img title="da city is beeg, yo" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421164333.png></p><p>Quick fade transition (???) into another angle of them exiting the building, framed between some wood slats, <strong>still in slow-motion</strong></p><p><img title="oh! camel :)" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421164419.png></p><p>CUT BACK TO THE FIRST ANGLE of them exiting AND THEY'RE <strong>STILL IN SLOW-MOTION</strong>.</p><p><img title=":o who's dat?" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421164633.png></p><p>You have to be kidding me. there is. another angle of them exiting. but from their right this time. Also framed between (different) wood slats. Also the dude is looking directly at the camera now.</p><p><img alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421164705.png title=candyman></p><p>Ah ok it was for the cut to this candy seller, who then nods, presumably at the dude he's making eye contact with. The candy seller is out-of-focus the entire shot, only the low-angle close-up of the candy is in focus.</p><p><img alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421165213.png title=👀></p><p>Quick cut back to this guy. And his eyes jerkily move further to his right, and...</p><p><img title="pretty colors" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421165617.png></p><p>Cut to this guy selling colored thread, also presumably looking at the dude. Still low-angle with only the thread in focus. There's a whoosh sound effect and some speedup/slowdown and some inserted black frames to create a flashing effect for extra drama.</p><p>Back to the dude looking around, and then...</p><p><img title="i have 2 plates. take it or leave it" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421173713.png></p><p>Now it's a guy selling plates, there's <em>more</em> whooshing and flashing and slow downs, and he's <strong>still out-of focus</strong> with only the close-up low-angle plates in-focus.</p><p>You know the drill. <em>Whoosh</em> cut back to the guy, <em>whoosh</em> cut to...</p><p><img title="mm yummy snacc" alt src=https://static.wolfgirl.dev/434b2dd7-bd56-498c-8477-87cb23c76ab3/Pasted%20image%2020240421173817.png></p><p>A guy selling food on sticks. This time from above! with the food in-focus and the guy out-of-focus. ok so clearly the out-of-focus and speed changes were a stylistic choice here. I am not sure why they felt the need to do this four times in a row, but apparently they thought it was important to note there were four street vendors looking directly at this dude as soon as he exits the building. Good to know.</p><p>There are many more rapid-fire cuts in this scene of the dude and his attendant walking to different booths, but at this point I had already lost it and needed to pause.</p><p>I hope the breakdown of this minute-long clip gives you a sense of what the directing/editing is like for the <em>entire remaining 40 minutes</em>: sloppy, over-dramatic, with musical cues constantly telling you what you should be feeling at any given moment. It's sort of like a toned-down version of <a href="https://www.youtube.com/watch?v=APHcYegE6ns">Most dramatic scene ever</a>, but still not pleasant to watch unless one is used to it.</p><p>Last thing: you may have also noticed I didn't bring up this scene in the plot summary earlier. That wasn't just because the episode was too long, that's because this scene is nothing: it doesn't matter, nothing shown here pays off in any way during the rest of the episode, it's <em>pure filler</em>. When so much of the <strong>first episode</strong> is like this, it was just too exhausting for me to want to continue.</p><h2><a aria-hidden=true class=anchor href=#themes id=heading-themes></a>Themes</h2><p>It's also not quite fair to compare the overall themes for these, because I've read all of Song Of The Long March and have a complete understanding of what it's about, whereas I only get hints at themes from The Long Ballad, and who knows maybe it gets better! Still, even from the first episode I can opine about what I think it wants to do, so opine I shall.</p><h3><a aria-hidden=true class=anchor href=#song-of-the-long-march-2 id=heading-song-of-the-long-march-2></a>Song Of The Long March</h3><p>What's interesting to me is there's quite a strong gender theme in this manga. Li Chang Ge not only disguises herself as a boy and actively goes out of her way to be thought of as a boy, but even laments how she felt more at home in the palace learning about military strategy with the men (but could never be accepted fully due to her gender), and spent all her time there instead of learning how to be a proper princess (because the only female role-model in her life, her mother, shunned her from birth<sup class=footnote-ref><a data-footnote-ref href=#fn-5 id=fnref-5>5</a></sup>).</p><p>It's not a <em>trans</em>-gender reading, though, since:</p><ol><li>She never expresses a desire to <em>be</em> the other gender, only really pushing against the rigid social hierarchy and doing what she must to achieve her goals. To the extent she enjoys being treated like just another boy, that's mostly her enjoying being freed from the social stigma of living as a girl under patriarchy.</li><li>Once she comes into contact with strong female role models, like the captured Uighur who looks like her mom and teaches her about period cramps, or the temple priestess who's not-so-secretly running an underground trading business, or her long-lost grandmother, does she discover how to be comfortable in her own skin and reveal her secret to more people.</li></ol><p>It could be said this sort of theme is common among coming-of-age stories, just most focus on boys doing boy things. Yet, I think this story is more than just "girl doing girl things", and the topic is treated in a pleasingly thoughtful way. It's very gender, the process of self-discovery as explored throughout the series, and it resonated with me.</p><p>Other themes the manga focuses on are the realities of war, figuring out what your aspirations are, how to live with repeated trauma, and the importance of family, both blood & found. All in all, great stuff, handled pretty well I'd say, nice deep themes that really engross you in all the characters' lives.</p><h3><a aria-hidden=true class=anchor href=#the-long-ballad-2 id=heading-the-long-ballad-2></a>The Long Ballad</h3><p>Again, I only watched the first episode so can't comment on the themes of the work as a whole, but from that first episode it seems this adaptation is not nearly as interested as exploring the themes I listed previously. Instead, it seems to be more about forbidden romance, the theatrics of war, and castle politics. Which, sure! Those are themes you can have. And yet, those themes are <em>still executed more poorly</em> than in Song Of The Long March. Combined with the jarring aesthetics, I saw no reason to continue and dropped it at the first episode.</p><h2><a aria-hidden=true class=anchor href=#conclusion id=heading-conclusion></a>Conclusion</h2><p>I hope it's now clear why I prefer the original manga over the TV adaptation. I also hope you had fun reading this, and are maybe even convinced to give the manga a shot yourself. It's fairly short all things considered, only 61 chapters, and I do highly recommend it.</p><p>Potentially not as highly as my favorite manga like <a href=https://mangafire.to/read/sayonara-eri.m3qx/en/chapter-0>Goodbye, Eri</a> or <a href=https://mangafire.to/read/kagurabachi.v92q7/en/chapter-1>Kagurabachi</a>, which are all things you should read immediately, but I'll write about those more some other time maybe.</p><section class=footnotes data-footnotes><ol><li id=fn-3><p>I hope y'all appreciate all the alt text and title text I put on these images :3 <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-3>↩</a></p></li><li id=fn-2><p>dude should have moved out of the way sooner, guard had no reason to be going on his horse that fast, Li Chang Ge doesn't need to be this harsh <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-4><p>I point this out now because it's very much not the case in the manga, as I'll point out later<sup class=footnote-ref><a data-footnote-ref href=#fn-5 id=fnref-5-2>5</a></sup> <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-4>↩</a></p></li><li id=fn-1><p>as a brief aside, I will take a moment to complain about the translation quality. The first couple chapters are pretty rough until they get a new translation team who actually knows what they're doing, and it almost made me stop wanting to read. Even at its best, their are strange flows where the paneling and the translation don't exactly line up, probably due to differences in each language's sentence structure. Really goes to show how hard a problem translation is, and how much I've been taking for granted all the high-quality official translations there are these days. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-1>↩</a></p></li><li id=fn-5><p>Hey! It's later! Yes, this does mean there's an integral aspect of Li Chang Ge's character the show simply cannot reproduce, and whatever it tries to do will be much worse. But I'm sure you've already figured that out by now :) <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-5>↩</a> <a aria-label="Back to reference 5-2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5-2 href=#fnref-5-2>↩<sup class=footnote-ref>2</sup></a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[added this to my .zshrc]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/5425428/" />
      <id>https://wolfgirl.dev/blog/5425428/</id>
      <published>2024-04-04T22:35:35Z</published>
      <updated>2024-04-04T22:35:35Z</updated>
      <summary><![CDATA[In which I show off a pretty bizzare shell snippet & also explain it.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost in 2 parts.</p><hr><pre class=syntax-highlighting><code class=language-zsh><span class=string>alias</span> <span class=string>prune</span>=<span class=string>"git remote prune origin | sed -n -E '/polywolf/ s/.*(polywolf.*)/\1/p' | xargs git branch -D"</span></code></pre><p>because i found myself typing it out <em>from memory</em> that often</p><p>oh no... I'm becoming one of Those girlies aren't i...</p><h2><a aria-hidden=true class=anchor href=#polywolf-what-does-this-do id=heading-polywolf-what-does-this-do></a>"PolyWolf, What Does This Do?"</h2><p>i'm glad you asked!! let's break it down:</p><blockquote><p><strong>NOTE</strong>: this assumes some familiarity with how git branches work. See <a href=https://wizardzines.com/comics/remote-branch-caching/>this</a> and potentially <a href=https://wizardzines.com/comics/branch-pointer-commit/>this</a> for quick explainer</p></blockquote><h2><a aria-hidden=true class=anchor href=#goals id=heading-goals></a>Goals</h2><p>in git, I like to use short-lived feature branches that I then rebase onto <code>main</code>, squash to a single commit, and delete<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>. Turns out this style of git is so common that GitHub has a dedicated option for doing this with a single click on a pull request.</p><p>The problem with the GitHub click is that, while the branch is deleted in the remote repository, the branch you were working on locally is still around and clutters up <code>git branch</code> output and any tab-complete things you have set up. So! What we want to do, then, is have a command that you can run periodically to delete local branches that were deleted on the remote.</p><h2><a aria-hidden=true class=anchor href=#getting-a-list-of-branches-to-delete id=heading-getting-a-list-of-branches-to-delete></a>Getting A List Of Branches To Delete</h2><p>This is actually a tricky UX question! How do you know the difference between a "deleted branch" and "a branch you haven't pushed yet"? The answer is, you don't! There is no way for git to know this information 🙂</p><p>But! We can still do something that's almost as good: getting a list of branches that have been deleted on the remote since the last time you did a prune. There is a command for this: <code>git remote prune origin</code>. This will look at all the locally-cached remote-tracking branches (the ones starting with <code>origin/</code>), and delete any that are no longer present on the remote.</p><p>This is <em>almost</em> exactly what we want. But as I said earlier, even if the branch <code>origin/polywolf/feature-branch</code> is deleted, <code>polywolf/feature-branch</code> will stick around. Fortunately, <code>git remote prune origin</code> prints out what it does. Unfortunately, there are no formatting options for its output, so we resort to Manually Parsing It.</p><h2><a aria-hidden=true class=anchor href=#editing-streams-with-the-stream-editor id=heading-editing-streams-with-the-stream-editor></a>Editing Streams With The Stream EDitor</h2><p>this is in fact what <code>sed</code> is meant to do! Let's break down the specific invocation I use:</p><h3><a aria-hidden=true class=anchor href=#the-arguments id=heading-the-arguments></a>The Arguments</h3><p>From <code>sed --help</code></p><ul><li><code>-n</code>: "suppress automatic printing of pattern space". Basically, only output lines when we tell it to</li><li><code>-E</code>: "use extended regular expressions in the script". Basically, allow for replacements by capture groups<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>2</a></sup>.</li><li>the last argument is interpreted as a sed script, which we will need to go to the manpage for</li></ul><h3><a aria-hidden=true class=anchor href=#the-script id=heading-the-script></a>The Script</h3><p>From <del><code>man sed</code></del> (let's be real here) searching on Stack Overflow:</p><ul><li><code>/polywolf/</code>: only run on lines that have "polywolf" in them</li><li><code>s/.*(polywolf.*)/\1/p</code>:<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3-2>2</a></sup> For every line that looks like <code>* [pruned] origin/polywolf/feature-branch</code>, print out just <code>polywolf/feature-branch</code>. I verified this regex experimentally :)</li></ul><p>Great! We've collected all the branches we need into a "list" (newline-separated strings), and now we just need to delete them. But how?</p><h2><a aria-hidden=true class=anchor href=#deleting-a-branch id=heading-deleting-a-branch></a>Deleting A Branch</h2><p>The command to delete a branch is <code>git branch -d</code>. However, in this normal delete, git will complain the branch hasn't been merged. We don't care about this, because our feature branches have been effectively added as a commit on <code>main</code>, so we need to tell git "yes we really want to delete this branch", which we can do with <code>git branch -D</code>.</p><h3><a aria-hidden=true class=anchor href=#deleting-all-the-branches id=heading-deleting-all-the-branches></a>Deleting ALL The Branches</h3><p>The typical way of iterating over a list in bash-likes is</p><pre class=syntax-highlighting><code class=language-bash><span class=keyword>for</span> <span class=property>branch</span> <span class=keyword>in</span> <span class=embedded>$(</span><span class=function>the-expression</span><span class=embedded>)</span>
<span class=keyword>do</span>
  <span class=function>git</span> branch <span class=constant>-D</span> <span class=string>"</span><span class=operator>$</span><span class=property>branch</span><span class=string>"</span>
<span class=keyword>done</span></code></pre><p>This is the same as running <code>git branch -D b1; git branch -D b2; git branch -D b3; ...</code>, which, while somewhat inefficient from starting so many processes, Just Works™️. But! As it turns out, <code>git branch -D b1 b2 b3 ...</code> has the same effect, so we can use <code>xargs</code> (I absolutely adore <code>xargs</code>, it is so useful in one-liners).</p><p><code>xargs</code> takes in a "list" on standard input, and then turns that into an argument list it appends to whatever command you give it. So, given the list of branches we have currently, <code>xargs git branch -D</code> does exactly what we want!</p><p>And there you have it! A comprehensive explanation of a 1-liner I wrote for my own purposes that will probably be useful to no one else. This is the power of custom aliases/scripts, u can really tailor your own shell experience. it's almost <em>too</em> powerful... i still have a lot to learn tho!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I did a couple internships at a <a href=https://www.mercurial-scm.org/>Mercurial</a> shop and have sworn by this model ever since. You Will Never<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>3</a></sup> Convince Me Merge Commits Are Good <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-3><p>this is fancy Regex speak. don't worry about it kitten <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-3>↩</a> <a aria-label="Back to reference 2-2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2-2 href=#fnref-3-2>↩<sup class=footnote-ref>2</sup></a></p></li><li id=fn-2><p>ok Never is a strong word but u get my point <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[wow another weekend project]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/4999112/" />
      <id>https://wolfgirl.dev/blog/4999112/</id>
      <published>2024-03-10T14:15:22Z</published>
      <updated>2024-03-10T14:15:22Z</updated>
      <summary><![CDATA[I show off a (probably very bad) lock-free header-only C library.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>Was talking with a friend, and he said something to the effect of "man, if only I had a lock-free ringbuffer right now..." I thought I had one of those lying around somewhere, but turns out I only sort of did:</p><ul><li>It was written in Rust, not C (hard to include in a random C project)</li><li>It only supported pushes and pops of single elements (inefficient)</li><li>It suffered from multiple logic bugs (atomic programming is hard!)</li></ul><p>So! To prove to myself that I can still write C code in a pinch, I decided to knock one out real fast. And I did! <a href=https://github.com/p0lyw0lf/silly_ringbuffer>https://github.com/p0lyw0lf/silly_ringbuffer</a> took a bit longer than expected (setting up C environment on Windows, solving some hard bugs like ABA), but overall I am very satisfied with the results.</p><h3><a aria-hidden=true class=anchor href=#included-features id=heading-included-features></a>Included Features</h3><ul><li>Pushes and pops of arbitrary sizes (up to size of ringbuffer)</li><li>Threadsafe (safe to use without any external synchronization primitives)</li><li>Lock-free (only uses atomic variables internally)</li><li><del>Wait-free (threads always make progress if they can, and return an error if they can't)</del> maybe not?</li></ul><h3><a aria-hidden=true class=anchor href=#missing-features id=heading-missing-features></a>Missing Features</h3><ul><li>No support for custom allocators (just uses <code>malloc</code> and <code>free</code>, tho this is the easiest to remedy)</li><li>No growable buffer (size must be known up-front)</li><li>No "pop everything remaining" operation</li><li>No synchronization-primitive-enabled variant (would be more efficient in cases of multiple readers or writers, currently there is just one part where we have to spin which is bad)</li></ul><p>I might add these features sometime much later, but for now I consider it "good enough"; I have proven the point to myself, and my friend can continue with his own silly C project (he wanted a lock-free ringbuffer because "sharing a file across a <code>fork()</code>" isn't cross-platform enough, doesn't work on Windows).</p><p>Also, despite being a hackathon-like project, I always write my code with lots of documentation, so <a href=https://github.com/p0lyw0lf/silly_ringbuffer/blob/6400f311e0eb9f26f5bd5ed89c00a62b096a98dc/srb.h#L86>go read it!!!</a> you might learn something <del>or find another bug idk</del></p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[PolyWolf Reviews: PSYCHO-PASS: Providence]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/4935145/" />
      <id>https://wolfgirl.dev/blog/4935145/</id>
      <published>2024-03-07T03:40:45Z</published>
      <updated>2024-03-07T03:40:45Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>I completely forgot that nothing besides the first season of PSYCHO-PASS was any good. This movie didn't do a whole lot to change my mind about that.</p><p>Not a lot of the plot points made any sense. Fistfight scenes were well animated in terms of realism, but were often dragged out and not well composited. All of the dramatic deaths clearly had their final scene planned with no regard to how they got to that point. Everyone has as aversion to helmets and ear protection in gunfights. Pacing was all over the place.</p><p>However! Some of the new tech was pretty cool. Loved the twin-engine fly-by-wire drone, and while the communication station didn't make sense for how it stayed in the air, its design is rule-of-cool & it even has realistic-ish laser weapons!! (helpfully colored green for viewing pleasure but hey their range and damage is realistic ok). I also really appreciated the background work done in the automated city ruins, very cool mysterious stuff that didn't need over-explanation. Lots of other background work was spectacular too, lot of the building interiors looked great.</p><p>Unfortunately there are still a lot of technological missteps in this sci-fi film. I could accept the original Dominator's rule of cool, "it's some sort of beam", but the souped-up one's energy ball thingy is just too outlandish. Don't even get me started on The Divider (stupid-ass brain chip), or how the inside of the submarine looks like a luxury apartment.</p><p>Also, while I really really liked the ending, and thought it was paced well, and was thematically satisfying, the question it poses there should honestly have been the main point of the film. "What happens when someone commits a crime in broad daylight but the Dominators don't react?" is so much more interesting than "What if there was a second Sybil System that talks like God and whose followers want to use it to control all wars somehow? (never mind the details about how, exactly (no we're not going to explain their supply chain))". The rest of the movie just took <em>so long</em> to get going to that one good part.</p><p>2/10 not worth it, go watch the original PSYCHO-PASS</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[is it just me or is Nix not a good language]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/4488642/" />
      <id>https://wolfgirl.dev/blog/4488642/</id>
      <published>2024-02-16T02:22:18Z</published>
      <updated>2024-02-16T02:22:18Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>I am writing in it following along with <a href=https://nix.dev/tutorials/module-system/module-system>https://nix.dev/tutorials/module-system/module-system</a> and without the safety net of statically analyzed types there is just this vague sense of unease that my code will be wrong and I won't know.</p><p>don't get me wrong it is very cool that they wrote an entire metaprogramming and typechecking system within Nix itself, to make sure module inputs/outputs are correct, but uh, it seems like that was pretty clearly in spite of the language, not because of it. and now there's so much magic going on behind the scenes that i have to read this 3-hour long tutorial just to understand the surface layer.</p><p>anyways. I am still glad such a tutorial exists because without it I would be even more lost. docs like this r good. this offsets all the possible badness from the language being dynamically-typed, clearly</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Up And To The Right]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2024-02-01-up-and-to-the-right/" />
      <id>https://wolfgirl.dev/blog/2024-02-01-up-and-to-the-right/</id>
      <published>2024-02-02T00:00:00Z</published>
      <updated>2024-02-02T00:00:00Z</updated>
      <summary><![CDATA[A rumination on buildings and photos and dreams]]></summary>
      <content type="html"><![CDATA[<p>There is a small story in the alt text; click each image to read it.</p><style>.grid{grid-template-columns:repeat(3,1fr 0);display:grid}.grid p{margin:0}button.show{border:none}</style><div class=grid><p><img alt="Logically, I know that what I dream about is simply my brain subconsciously processing recent information" src=https://static.wolfgirl.dev/2024-u2tr/01A_0M8A9787.webp></p><p><img alt="But it sure does smart when all I can remember dreaming about is just stressful situations." src=https://static.wolfgirl.dev/2024-u2tr/01B_0M8A9787.webp></p><p><img alt="(I keep a dream log, by the way.)" src=https://static.wolfgirl.dev/2024-u2tr/01C_0M8A9787.png></p><p><img alt="It's not like I write down every dream I have, mind you." src=https://static.wolfgirl.dev/2024-u2tr/02A_0M8A9785.webp></p><p><img alt="But the vivid ones, and the ones that make it into the log, those are always of a nightmare-ish variety." src=https://static.wolfgirl.dev/2024-u2tr/02B_0M8A9785.webp></p><p><img alt="Placed in an awkward social situation making small talk with someone whose name I've forgotten after a test that I both overslept & forgot to study for, but it's really important I recruit them to my party to help save the world from school shooters." src=https://static.wolfgirl.dev/2024-u2tr/02C_0M8A9785.png></p><p><img alt="Knowing that, when I wake up, nothing will matter anymore, doesn't make the fear while I'm dreaming any less real." src=https://static.wolfgirl.dev/2024-u2tr/03A_0M8A9761.webp></p><p><img alt="There are a lot of frightening things happening outside in my very own United States of America right now, yet I'm over here cooped up in bed spooking myself??" src=https://static.wolfgirl.dev/2024-u2tr/03B_0M8A9761.webp></p><p><img alt="Coming back full-circle, maybe it's a way of dealing with the fear that doesn't require me to break my rosy facade during the day." src=https://static.wolfgirl.dev/2024-u2tr/03C_0M8A9761.png></p><p><img alt="I want people to like me for who I am, what I can do, and how I make them feel." src=https://static.wolfgirl.dev/2024-u2tr/04A_0M8A9822.webp></p><p><img alt="If posting some cool photos on my website is a way to accomplish that, then great!" src=https://static.wolfgirl.dev/2024-u2tr/04B_0M8A9822.webp></p><p><img alt="But here's the truth: this art means nothing, this &#34;essay&#34; means nothing, and no one cares what I think, because no one's going to read this." src=https://static.wolfgirl.dev/2024-u2tr/04C_0M8A9822.png></p><p><img alt="Is what I would say, if I were depressed, haha lol." src=https://static.wolfgirl.dev/2024-u2tr/05A_0M8A9791.webp></p><p><img alt="Photography has always been one of my ways to express my frustration and burnout, but you know what's even better?" src=https://static.wolfgirl.dev/2024-u2tr/05B_0M8A9791.webp></p><p><img alt="Human beings you can talk to in-person, preventing you from going crazy." src=https://static.wolfgirl.dev/2024-u2tr/05C_0M8A9791.png></p><p><img alt="...I should try harder to not end this on a somber note." src=https://static.wolfgirl.dev/2024-u2tr/06A_0M8A9757.webp></p><p><img alt="Still, things are looking pretty bleak, man." src=https://static.wolfgirl.dev/2024-u2tr/06B_0M8A9757.webp></p><p><img alt="I love you, don't you EVER forget that, sweet dreams, see you when you wake up." src=https://static.wolfgirl.dev/2024-u2tr/06C_0M8A9757.png></p></div><p>Canon EOS 5DS 135mm, 100ISO</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Assembly or Bash?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/3982419/" />
      <id>https://wolfgirl.dev/blog/3982419/</id>
      <published>2023-12-26T05:18:07Z</published>
      <updated>2023-12-26T05:18:07Z</updated>
      <summary><![CDATA[In which I jokingly compare the relative merits of the Assembly and Bash programming languages.]]></summary>
      <content type="html"><![CDATA[<p>Originally published to Cohost with no title, as a response to an ask "assembly or bash?" from <a href=https://welltypedwit.ch>Alice</a></p><hr><p>a question with multiple interpretations! i'm going to have fun with this :3</p><h2><a aria-hidden=true class=anchor href=#which-would-i-rather-use id=heading-which-would-i-rather-use></a>Which Would I Rather Use?</h2><p>It depends, obviously. I'd use assembly for file operations and bash for SIMD, obviously. Wait no I g-</p><h2><a aria-hidden=true class=anchor href=#which-would-i-rather-write id=heading-which-would-i-rather-write></a>Which Would I Rather Write?</h2><p>Both can be a pain, and for similar reasons: some old white dudes made some technical decisions forever ago and those decisions may have been OK then but now we're stuck with them for backcompat purposes and honestly we should probably have moved on by now but oh well backcompat too stronk.</p><p>For me, though, the pain of assembly makes me feel like a wizard trying to find the right runes but a little demon pokes my brain with a pitchfork every time I get one wrong, while the pain of bash makes me feel like I'm talking to a senile grandpa who keeps extoling "back in my day" while changing his story every half hour but I can't really leave since I'm his assigned nurse this shift. So the answer to this one is assembly.</p><h2><a aria-hidden=true class=anchor href=#which-sounds-cooler-as-a-magic-spell id=heading-which-sounds-cooler-as-a-magic-spell></a>Which Sounds Cooler As A Magic Spell?</h2><p>bash. assembly maybe constructs you a wooden table, but bash lets you create dents in anything proportional to the mana input, and some mages found that could include your opponent's heart, leading to a low-mana instakill spell that revolutionized the magical meta once that was discovered.</p><h2><a aria-hidden=true class=anchor href=#closing-thoughts id=heading-closing-thoughts></a>Closing Thoughts</h2><p>it seems bash and assembly are tied here. oh well guess the question couldn't be answered after all sorry</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Stretching]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/3922530/" />
      <id>https://wolfgirl.dev/blog/3922530/</id>
      <published>2023-12-20T02:53:33Z</published>
      <updated>2023-12-20T02:53:33Z</updated>
      <summary><![CDATA[I describe my stretching routine!]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost as a response to an ask, "What's your stretching routine, if you don't mind me asking?".</p><hr><p>Great question! TL;DR: deep lunges ~8min each as the main focus, with {side splits, wide stance bend-over, cross-legged bend-over, calf stretches, single-leg reaches} for ~2min each if I'm feeling up for more. If those words don't mean anything to you, keep reading! I made diagrams!</p><p><sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> My routine is very cobbled-together and unscientific. I took most of my inspiration from when my sister (who does ballet) led me through a shortened version of her routine. From that, I took mostly the ones I could do without much warmup and added in a few from sports practices. The end result is something targeted specifically to address imbalances from too much running and sitting. I haven't made much progress in flexibility in a while so ymmv, but I <em>can</em> touch my toes so it'll prob get u there at the very least.</p><p>Do note, however, that because of the nature of the things I want to stretch, these are all lower-body ones, no upper-body. So if you're looking for that, sorry, I need to figure out a better routine myself before giving any advice.</p><h2><a aria-hidden=true class=anchor href=#general-stretching-advice id=heading-general-stretching-advice></a>General Stretching Advice</h2><p>Like with any physical activity, stretching is a conversation with your body! It is important to really pay attention to how things feel and back off if things start to hurt in bad ways. It is also important to learn to distinguish between "good hurting" and "bad hurting". It's hard for me to describe beyond that through text, sort of a "you know it when you feel it" sort of thing. "Good hurting" is like the soreness you get from a deep stretch or workout, "bad hurting" is when you injure yourself doing those things. It's sometimes a fine line in terms of what activities will cause what sensation, especially when pushing your limits, but the sensations themselves are very distinct I promise!!</p><h2><a aria-hidden=true class=anchor href=#deep-lunges id=heading-deep-lunges></a>Deep Lunges</h2><p><img alt="deep lunge, hips toward the ground, keeping back flat, using one or both arms to brace" title="deep lunge, hips toward the ground, keeping back flat, using one or both arms to brace" src=https://static.wolfgirl.dev/9331ce3f-3b64-4c97-9035-33183f99d128/deep_lunge.png></p><p>Before getting into the rest of the stretches, I should probably explain something about the images I've included. In green, I've highlighted parts of my body that <em>should</em> be feeling the stretch, and in red, I've highlighted parts that <em>should not</em> be the focus of the stretch. In this case, we see that the <strong>hip flexors</strong> and <strong>upper hamstring/lower glute</strong> are the focus. The goal is eventually to get your elbows on the ground, but you should not be twisting or rounding your back too much in order to do so<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>. I've also included arrows on the "direction" of the stretch, that is, what part of your body should be moving lower in order to make the stretch deeper. In this case, getting your hips closer to the ground is the goal.</p><p>These are my favorite stretch and I just love how my legs feel afterwards. I do it for the longest because the position is fairly stable, and I can really feel the muscles open up as time goes on. Something about the upper hamstring and especially the opening of the hip flexor just feels really nice... and boy howdy do my hip flexors need it from sitting all day<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup>.</p><h2><a aria-hidden=true class=anchor href=#side-splits id=heading-side-splits></a>Side Splits</h2><p><img alt="side splits, using an object in front to brace arms" title="side splits, using an object in front to brace arms" src=https://static.wolfgirl.dev/9331ce3f-3b64-4c97-9035-33183f99d128/side_splits.png></p><p>I started doing this on a whim and I've actually seen the most improvement here! I've been able to move my feet a solid couple inches farther than when I started. These can be potentially dangerous because your full bodyweight is on the tendons in a way that is hard to back out of without activating those same tendons, creating a risk for tears. I always use my hands to push myself up first, and then slowly shuffle my feet together until I'm out of the stretch.</p><h2><a aria-hidden=true class=anchor href=#wide-stance-bend-over id=heading-wide-stance-bend-over></a>Wide Stance Bend-Over</h2><p><img alt="standing, legs apart, straight legs, straight back, leaning over" title="standing, legs apart, straight legs, straight back, leaning over" src=https://static.wolfgirl.dev/9331ce3f-3b64-4c97-9035-33183f99d128/wide_bend_over.png></p><p>a.k.a "<a href=https://knowyourmeme.com/memes/jack-o-crouch-jack-o-challenge>The Jack-O Crouch</a>". As you can see pictured, I am not nearly as flexible as that character, but this is a good stretch regardless. The focus should definitely be on keeping your back <em>flat</em> throughout. The goal is to stretch your hamstrings here, not your upper back (a bit of lower back stretch is unavoidable). The mental image should be rotating your hips. If you're like me, and your hips aren't very flexible, you might be surprised at how little they can rotate! This is ok, and means you have lots of room for improvement. My eventual goal is go <a href=https://static.wolfgirl.dev/art/_2022-07-baseball.jpg>below 90 degrees</a> but oh boy my sitting legs r not up 2 task; my lower hamstrings are very tight.</p><h2><a aria-hidden=true class=anchor href=#cross-legged-bend-over id=heading-cross-legged-bend-over></a>Cross-Legged Bend-Over</h2><p><img alt="standing, legs crossed, straight legs, straight back, bending over" title="standing, legs crossed, straight legs, straight back, bending over" src=https://static.wolfgirl.dev/9331ce3f-3b64-4c97-9035-33183f99d128/cross_bend_over.png></p><p>Variant of the previous stretch, targeting a different part of your hamstring than before. I'm not entirely sure myself if the stretch is more on the front crossed leg or the back, both are stretched, alternating between is probably fine. Remember to still keep your back flat! I usually don't go as low on these compared to the wide stance ones.</p><h2><a aria-hidden=true class=anchor href=#calf-stretches id=heading-calf-stretches></a>Calf Stretches</h2><p><img alt="standing, one leg forward, and one leg back, leaning with back flat over the front leg, stretching the back leg" title="standing, one leg forward, and one leg back, leaning with back flat over the front leg, stretching the back leg" src=https://static.wolfgirl.dev/9331ce3f-3b64-4c97-9035-33183f99d128/calf.png></p><p>I got a mean calf cramp once, so this made it into my routine. I also usually combine these with a standing lunge (in cyan) to target hip flexors again, since they're basically the same foot position just with different hip rotations. The other cyan is an option to stretch the front of your opposite foot. Make sure to do these in shoes or on a sticky exercise mat so your feet don't slip!</p><h2><a aria-hidden=true class=anchor href=#single-leg-reaches id=heading-single-leg-reaches></a>Single-Leg Reaches</h2><p>A classic, I think everyone had to do this in middle school for fitness tests or something?? Anyways this was the one fitness test i failed (: and I'm still working on it to this day. I usually do these flat with one leg butterfly-ed (as shown in <a href=https://static.wolfgirl.dev/art/_2021-06.png>this image</a> that I drew previously).</p><h2><a aria-hidden=true class=anchor href=#other-stretches-that-are-nice id=heading-other-stretches-that-are-nice></a>Other Stretches That Are Nice</h2><p>... but I didn't have room to include since I don't do them too regularly and this post is as long enough as it is:</p><ul><li>Butterfly</li><li>Single-Leg Quad</li><li>Single-Leg Sits</li><li>World's Greatest</li><li>Front Split Reaches And more upper body ones that, again, I'm neglecting to mention.</li></ul><p>That's about it, now, go stretch!! & drink some water, it'll make u feel better!! Physicality affecting emotions is real, and stretching is a good meditative/mindfulness thing, for me at least. thank u for allowing me 2 share my niche interest :)</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I'll be honest: I did forget about this eventually. I had the draft, I had the photos, main hurdle was actually setting aside chunks of time to draw over the pictures and edit the draft. I forgot about it in the meantime multiple times. Sorry for the big delay on this, I'll try to be faster in responding to asks in the future! Asks are always open after all, and more asks about physical activities are always appreciated! <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>A little is fine, since that can change the stretch on your legs in ways that can help, but again, backs are not something that needs too much stretching. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>Stretching theory time! If your lower back hurts from too much sitting, that tends to be a problem with your <strong>hip flexors</strong> being too tight, rotating your pelvis forward. So, my first stretch targets this hip flexibility a lot, and I throw in lower hamstring flexibility as other stretches to round things out. <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Weekend Project Complete]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/3587148/" />
      <id>https://wolfgirl.dev/blog/3587148/</id>
      <published>2023-11-19T15:42:55Z</published>
      <updated>2023-11-19T15:42:55Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p><a href=https://github.com/p0lyw0lf/crossposter>https://github.com/p0lyw0lf/crossposter</a></p><p>For a while, I've had a tradition of, whenever I would find a cool computer security-related article online, I would post it to a channel in a long-inactive Discord. I joined this Discord in my Freshman year of college, and I've been keeping up the tradition ever since, so I think I'm approaching 4 years of it now? Wild to think about.</p><p>The Problem with this is, my audience is limited. There are like <em>mayyyybe</em> 4 other people watching that channel, likely only 1. Plus, Discord is not a reliable store for information. So at one point, I decided to start backing things up <a href=https://wolfgirl.dev/cybersec/>on my blog</a>. Unfortunately, this was a very manual process, because interacting with Git is a lot harder than just pasting a link and some text into Discord, so the friction meant I very rarely updated that page.</p><p>During the initial Fediverse craze, one of my Solutions was to create an account, <a href=https://infosec.exchange/@PolyWolf>@PolyWolf@infosec.exchange</a>, that would take the place of my Discord channel. Problem was, that somehow ended up having even less of an audience because lol bootstrapping problem<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>. I was using that account more for following other tech accounts, than I was for posting myself.</p><p>So finally, while I was talking with the 1 friend who I knows follow the channel, I had an idea: why not write a little bot to automatically crosspost from my cozy Discord channel to these other platforms? That way, I could have the maximum audience with the least amount of effort.</p><p>The only thing stopping me was the good 'ol "I programmed too much at work this week so now I want to enjoy my weekend doing anything else but programming." Fortunately, this week was light, so I found the spoons Saturday, banging thru the full implementation in 8 hours<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup>, and spent a bit more time this morning setting up a t4g.micro instance on AWS to host it. And bam, it's done!</p><p>I have created a new Discord server for this bot, and will likely post my links there in the future. If this sounds interesting and u want the server invite, DM me @polywolf on Discord. Otherwise, once the new Cohost API rolls out, I'm probably also going to add a crossposting target for it under a new page.</p><p>In summary, I am excited I still have the ability to smash things together and have them work out. That's all for now, thanks for reading!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>I somehow overcame the bootstrapping problem for the second time in my life ever on Co Host Dot Org thanks to @fullmoon 's initial list from Twitter (now X) and also "being a techy trans furry on the techy trans furry website". My first time was on Scratch Dot MIT Dot EDU thanks to "being a kid making simple and fun games on the simple and fun games made by kids website". <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>Why Python? For this exact reason. Unparalleled when it comes to gluing web things together. It was my first real programming language and I love it so. Like, i can just do <a href=https://github.com/p0lyw0lf/crossposter/blob/main/poster/template.py>stuff like this</a> and it just works because lol! This repo is definitely not a paragon of good Python style, but it sure is My Python style :) <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Software Packaging Is A Nightmare]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2613009/" />
      <id>https://wolfgirl.dev/blog/2613009/</id>
      <published>2023-09-24T23:23:42Z</published>
      <updated>2023-09-24T23:23:42Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost. I recall this one getting a fair amount of attention, which felt good because I put a fair amount of time into it.</p><hr><p>Recently, a friend shared <a href=https://gist.github.com/terabyte/15a2d3d407285b8b5a0a7964dd6283b0>this gist about how Amazon's internal buildsystem works</a>, and wow did that unlock some Opinions for me about software packaging.</p><p>Supposedly, Amazon's buildsystem "Brazil" works a little bit like <a href=https://nix.dev/tutorials/first-steps/>Nix</a>/<a href=https://nixos.org/manual/nixpkgs/stable/>NixPkgs</a><sup class=footnote-ref><a data-footnote-ref href=#fn-12 id=fnref-12>1</a></sup>, in that it has entirely reproducible builds based on package declarations of nearly every package in existence. But instead of just being able to choose a single snapshot of package versions<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>2</a></sup>, you can also pin to <a href=https://semver.org/>semver</a> of a set of packages, whose mutual compatibility is automatically enforced via unit tests when creating a new immutable build, so you always have the latest updates. This is <em>amazing</em>. Also like Nix, you can have:</p><ul><li>Two versions of a package installed on your system, since different environments just choose which version they want</li><li>Local overrides of packages for development/debugging environments</li><li>Binary releases since everything is reproducible</li></ul><p>And it all "just works"! My friend who currently works there says he loves it and building software has never been easier. Which is wild, given that:</p><blockquote><p>The main build driver is a mess 'o Perl scripts that generates Makefiles. The build system is bootstrapped by a minimal Perl script which assumes only base Perl deps and GCC are available, and downloads all other dependencies.</p></blockquote><p>...but hey, if it works, it works!</p><h1><a aria-hidden=true class=anchor href=#most-software-isnt-like-this id=heading-most-software-isnt-like-this></a>Most Software Isn't Like This</h1><p>First, some terminology, so that it's unambiguous what I'm talking about:</p><ul><li><strong>Package</strong>: The atomic unit of software. Libraries, applications, etc. Each package has an: <ul><li><strong>Interface version</strong>: Some identifier for other software to know a package supports certain capabilities. Hopefully, specified in some <a href=https://semver.org/>semver</a>-compatible way, although in practice lol lmao. Adding extra debug logging or fixing security bugs that no legitimate consumer would be affected by won't change an interface version.</li><li><strong>Build version</strong>: Some identifier that can be 1-to-1 correlated with differences in the produced binary<sup class=footnote-ref><a data-footnote-ref href=#fn-8 id=fnref-8>3</a></sup> for a package. This lets you distinguish between "the library i built with extra logging and a patch for the vuln" and "the library without those things". Also accounts for differences in build versions of dependencies!</li></ul></li><li><strong>Dependency</strong>: a package that another package requires to build and/or run. Often specified with interface versions, but can also be specified against a build version.</li><li><strong>Version Set</strong>: A collection of build versions of packages known to work well together. It's important that <em>build versions</em> work well together and not <em>interface versions</em>, because it is very possible to mess up semver.</li><li><strong>Environment</strong>: For a specific package you want to use, the set of all other packages that are capable of affecting it.</li></ul><p>As far as I am aware, there are two common methods for distributing software packages and creating environments to run them in. These aren't the <em>only</em> methods in existence, and not everything can be categorized exactly, but they are archetypical.</p><h2><a aria-hidden=true class=anchor href=#share-everything id=heading-share-everything></a>Share Everything</h2><ul><li>There is one central version set, containing every package, often with testing that they all work together.</li><li>Only one build version per package can be installed at any given time. If you want to have different build versions at the same time, you need to create different packages or create package aliases.</li></ul><p>This is a <em>classic</em> model for software environments. <a href=https://archlinux.org/>Arch Linux</a>, <a href=https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux>RHEL</a>, <a href=https://docs.python.org/3/installing/index.html>pip</a>, <a href=https://www.npmjs.com/>npm</a>, <a href=https://brew.sh/>Homebrew</a>, <a href=https://files.minecraftforge.net/net/minecraftforge/forge/>Forge</a>, if you can point at it and say "that's a Package Manager", it likely uses this model. There are differences in how often upgrades are released, how exactly semver pinning works, other jobs the package manager takes care of, but all of the examples I listed share these key traits<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>4</a></sup>.</p><p>Now, frankly, I think this model sucks big booty balls, pardon my language. Only being able to have <em>one</em> (1) version of a package officially installed is extremely limiting. Your package needs a build version of a dependency not in the central version set? Your options are to:</p><ol><li>Rename the dependency and install it globally.</li><li>"Install" the dependency outside the control of the package manager.</li><li>Give Up</li></ol><p>Option 1 is dumb because suddenly you are specifying interface/build versions as part of the package name, when it is literally the package manager's job to distinguish between these. Option 2 is dumb because suddenly you're rolling your own package manager with <code>CMakeLists.txt</code> and shell scripts when you have a perfectly good package manager right there<sup class=footnote-ref><a data-footnote-ref href=#fn-6 id=fnref-6>5</a></sup>. Option 3 is dumb because Winners Never Quit 😤</p><p>Sometimes, it's OK for packages to just live in their own little island of dependencies! Not everything needs to be global! It's frankly unacceptable that local installs are so poorly supported! However, it's possible to go too far the other way. Instead of sharing everything, you can instead:</p><h2><a aria-hidden=true class=anchor href=#share-nothing id=heading-share-nothing></a>Share Nothing</h2><p>If a package has dependencies, it will need to include its own way of putting them in its environment. There are ways for packages installed separately to be part of the same environment, but absent a package manager, these methods are rickety and best<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>6</a></sup> and infuriatingly buggy at worst. <em>Curiously enough</em>, consumer-grade operating systems like Windows and MacOS have this as their default approach. <em>Even more curiously</em>, a semi-recent push towards containerization technologies like <a href=https://www.docker.com/>Docker</a>, <a href=https://snapcraft.io/>snap</a>, <a href=https://www.flatpak.org/>flatpak</a>, and others have been pushing Linux software to be also distributed using this model. Why is this?</p><p>I hypothesize this model's popularity comes from the fact it is more likely to produce consistently working software. Linux distributions have been plagued by "works on my machine" or "works on my distro" problems for, like, ever. Share Everything, when you're trying to build packages outside the global version set, against different global version sets from different distros, or even the same distro as it evolves over time, is just <em>begging</em> to have frustrating build problems. <em>This</em> is why language-specific package managers with virtual environments feel like such an upgrade, <em>this</em> is why Docker is so popular. Your global environment has ghosts in it, invisible dependencies that haunt your build process, so isolating <em>everything</em> to trap the ghosts has its benefits for reproducibility.</p><p>Now, to be clear, the Share Nothing approach is not without its drawbacks. Requiring packages to bundle all their dependencies/exist within an isolated Share Everything fiefdom leads to a ton of bloat. I don't particularly <em>want</em> 5 copies of Tensorflow and PyTorch on my machine, but since I also don't want to try to shove all my one-off AI projects into a global Python environment, this is what I get stuck with.</p><h2><a aria-hidden=true class=anchor href=#what-the-approaches-are-missing id=heading-what-the-approaches-are-missing></a>What The Approaches Are Missing</h2><p>Let's lay out our <strong><em>Ideal Build System Requirements</em></strong> again:</p><ul><li>Reproducible builds: if a remote system can build it, your local system can too</li><li>Local overrides: not only can you build the package locally, you can swap it out for anything expecting that package</li><li>Remotely hosted binary releases: because you <em>deserve</em> to not have your fans take off and your disk explode every time you want to install some software</li><li>No global version set: you can have more than one version (major, minor, patch all included) of a package installed on your system, and things just don't care, since they use the one they were reproducibly built for</li><li>Semver <em>and</em> hash pinning: to enable dependency sharing if supported, but also for exact reproducibility if needed</li></ul><p>hm. Neither approach we've covered gets this right, or even comes close to doing all these<sup class=footnote-ref><a data-footnote-ref href=#fn-5 id=fnref-5>7</a></sup>! There are <strong>fundamental flaws in the way software packages are currently distributed</strong> that prevents these goals from being achievable.</p><h1><a aria-hidden=true class=anchor href=#boiling-the-ocean id=heading-boiling-the-ocean></a>Boiling The Ocean</h1><p>Now that we have a better understanding of what Share Everything and Share Nothing lack, we can appreciate why Amazon's Brazil is such an achievement. <em>Not only</em> does it let you isolate packages to only see their dependencies so everything is reproducible, it <em>also</em> lets packages share dependencies that provide the same interface version! Awesome! But what does it take to accomplish this, exactly?</p><h2><a aria-hidden=true class=anchor href=#technical-challenges id=heading-technical-challenges></a>Technical Challenges</h2><p>Not going to go too in-depth here (this post is long enough as it is LOL), but somewhat unsurprisingly, there aren't any compelling technical reasons we can't have this. Environment isolation at all levels has gotten quite good on the major OSes; why isn't that enough?</p><h2><a aria-hidden=true class=anchor href=#social-challenges id=heading-social-challenges></a>Social Challenges</h2><p>It's not that the technology doesn't exist, it's just that, <strong><em>no one has cared enough to put it into practice</em></strong>. "Why should <em>I</em> change how <em>I</em> build software", say the developers, the distro creators, "it works well enough for <em>my</em> use cases!".</p><p>Personally, I have built many pieces of software in environments just a teensy bit different from where they were supposed to be built, and have seen how bad it is. Every package is different, with its own magic incantations of scripts and command line flags and environment variables and build directories to get them working. As one commenter on the Brazil gist puts it:</p><blockquote><p>One major issue, in my experience, is the Brazil packaging concept doesn't have critical mass or would require boiling the ocean to get there. At Amazon, there's one package manager - Brazil. You want a Gem, NPM package, *.so, or JAR dependency? You add it to Brazil. Sometimes you go through great pain to add it to Brazil (especially when importing a new build system). But once the package is there, everything just works.</p></blockquote><p>Only nerds with obscene amounts of time on their hands have the ability to create an ecosystem. Package maintainers for Gentoo, NixPkgs, Guix, AUR, each take up their own divine hammers and bend the universe of software to their will. Everything "just works" when you're in the system, but for those of us unlucky enough to be developing software outside of it, we're living in an unending nightmare, where nothing works and everything is hard and no one has enough spare for a <a href=https://xkcd.com/927/>15th standard</a><sup class=footnote-ref><a data-footnote-ref href=#fn-13 id=fnref-13>8</a></sup> because we're just trying to build our silly little packages gosh darn it.</p><h2><a aria-hidden=true class=anchor href=#what-amazon-does id=heading-what-amazon-does></a>What Amazon Does</h2><p>In a nutshell, they throw money at the problem. Money for compute, to build <em>every single transitive dependent</em> when a package is published to make sure the listed interface version is semver-compliant. Money for storage, to host the entire history of software (source and binaries) so no deploys can possibly fail by missing old build versions. And most importantly, money for developers to port all software they want to use to this buildsystem.</p><p>This approach really only works for companies like Amazon, since the reward for them is in fact worth the cost. But what about the rest of us?</p><h2><a aria-hidden=true class=anchor href=#can-we-have-this-too id=heading-can-we-have-this-too></a>Can We Have This Too?</h2><p>I don't know, honestly. In my uninformed opinion, I'd probably say that NixPkgs and Guix are the closest things to what I've laid out, hitting all my Ideal Build System Requirements minus ones that are impossible thanks to No Money (semver pinning, total history<sup class=footnote-ref><a data-footnote-ref href=#fn-10 id=fnref-10>9</a></sup>). I have not used either extensively enough to judge the UX fully<sup class=footnote-ref><a data-footnote-ref href=#fn-9 id=fnref-9>10</a></sup>, though I will say I have heard terrible things about NixPkgs<sup class=footnote-ref><a data-footnote-ref href=#fn-11 id=fnref-11>11</a></sup> and nothing about Guix, neither of which is a good sign.</p><p>As an individual, it is not worth it for me to boil the ocean. I've gotten used to living in the nightmare, holding my Windows development environment together with duct tape & superglue, so I don't think I'll be making the switch any time soon. However, it takes a community to boil the ocean, so maybe now that my Arch install has collapsed in on itself, my next Linux install will be a reproducible one. I can only hope others feel the same.</p><section class=footnotes data-footnotes><ol><li id=fn-12><p>For a great explanation of the differences between these, see <a href=https://www.haskellforall.com/2022/08/stop-calling-everything-nix.html>this excellent blog post</a> by @fullmoon <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-12>↩</a></p></li><li id=fn-1><p>I don't actually know Nix that well, this was just my impression based on limited use + doc reading, please correct me on this point if I'm wrong! <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-1>↩</a></p></li><li id=fn-8><p>"What about source-only languages??" the source is a form of binary :) <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-8>↩</a></p></li><li id=fn-2><p>I think? Reasonable people could disagree on some aspects, maybe i am generalizing too hard but u tell me <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-2>↩</a></p></li><li id=fn-6><p>Now to be fair, a lot of language-specific package managers <em>do</em> support having "local installs" of packages that read from a specific directory instead of from a repository. This is more a dig at the worst languages for packaging, C and C++, but still applies for how you get other languages' custom packages into local directories in the first place. <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-6>↩</a></p></li><li id=fn-3><p>On Windows, you can put your .dll or .exe into the global <code>PATH</code> for other packages to access it<sup class=footnote-ref><a data-footnote-ref href=#fn-7 id=fnref-7>12</a></sup>, but this has the drawback of "oh shoot I'm using the wrong version of python.exe again, better reorder my <code>PATH</code>", so usually it's just best practice to put all the .dlls a package will need in the same folder. if you forget this best practice, your app runs with elevated privileges, and the <code>PATH</code> can somehow contain a user-writable folder, <a href=https://stackoverflow.com/questions/3623490/what-is-dll-hijacking>oops! DLL hijacking</a>. I think MacOS works similarly, but with better sandboxing in certain areas (so package files are only accessible by the package itself really, i think it is still susceptible forms of dll hijacking). for docker, u share dependencies through exposing network ports or shared folders, which, lol lmao. <a aria-label="Back to reference 6" class=footnote-backref data-footnote-backref data-footnote-backref-idx=6 href=#fnref-3>↩</a></p></li><li id=fn-5><p>Honestly? I believe that Cargo, the build system for Rust packages, comes the closest! Unfortunately the constraints of the language make binary releases nearly impossible (generics), and semver a thing only for source distributions. Plus, unless you're <a href=https://github.com/tosc-rs/mnemos>a madwoman</a>, Cargo isn't really suited for non-Rust packages. <a aria-label="Back to reference 7" class=footnote-backref data-footnote-backref data-footnote-backref-idx=7 href=#fnref-5>↩</a></p></li><li id=fn-13><p>It's also a hurdle that proprietary software won't adopt a model like this unless it is <em>the</em> standard, so anything that tries will have to bend over backwards for proprietary software, even more than for weird build scripts. But I digress, since this is ALREADY TOO LONG <a aria-label="Back to reference 8" class=footnote-backref data-footnote-backref data-footnote-backref-idx=8 href=#fnref-13>↩</a></p></li><li id=fn-10><p>https://hachyderm.io/@fasterthanlime/111103916027416609 <a aria-label="Back to reference 9" class=footnote-backref data-footnote-backref data-footnote-backref-idx=9 href=#fnref-10>↩</a></p></li><li id=fn-9><p>And when I did give it a little whirl, it promptly ate up all 72GB of my free disk space, which is maybe on me, but also, that's a lot of disk space!! so i haven't used it since. <a aria-label="Back to reference 10" class=footnote-backref data-footnote-backref data-footnote-backref-idx=10 href=#fnref-9>↩</a></p></li><li id=fn-11><p>https://blog.wesleyac.com/posts/the-curse-of-nixos , among others <a aria-label="Back to reference 11" class=footnote-backref data-footnote-backref data-footnote-backref-idx=11 href=#fnref-11>↩</a></p></li><li id=fn-7><p>Another thing you can do is register a different global environment variable just for your package, and then anyone looking for your package can simply read from that environment variable. For Docker, <code>s/environment variable/network port/g</code>. Like I said, rickety at best, infuriatingly buggy at worst :P <a aria-label="Back to reference 12" class=footnote-backref data-footnote-backref data-footnote-backref-idx=12 href=#fnref-7>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Learning Memory Order in C With Mutexes]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2091838/" />
      <id>https://wolfgirl.dev/blog/2091838/</id>
      <published>2023-08-10T01:13:59Z</published>
      <updated>2023-08-10T01:13:59Z</updated>
      <summary><![CDATA[In which I (hopefully simply) explain C11's memory orderings using mutexes as a motivating example.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>oh wow i have been mega procrastinating on this. <a href=/blog/2064554/>read this first btw</a></p><h2><a aria-hidden=true class=anchor href=#lets-talk-about-volatile id=heading-lets-talk-about-volatile></a>Let's Talk About <code>volatile</code></h2><p>So. <code>volatile</code>. The embedded programmer's best friend. But what does it actually mean?</p><p>From <a href=https://en.cppreference.com/w/c/language/volatile>cppreference</a>, emphasis mine:</p><blockquote><p>Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within <strong>a single thread of execution</strong>, a volatile access cannot be optimized out or reordered <strong>relative to another visible side effect</strong> that is separated by a sequence point from the volatile access.</p></blockquote><p>More simply, volatile reads and writes need to happen in the same order as you wrote them down in, but only with respect to "observable side effects" like syscalls or other volatile accesses.</p><p>So if you tried to use a standard <code>volatile int</code> for a mutex (ignoring the race condition and only focusing on a single thread):</p><pre class=syntax-highlighting><code class=language-c><span class=type>void</span> <span class=function>lock</span>(<span class=keyword>volatile</span> <span class=type>int</span><span class=operator>*</span> <span class=variable>m</span>) {
  <span class=type>int</span> <span class=variable>old_m</span> <span class=operator>=</span> <span class=number>1</span>;
  <span class=keyword>while</span> (<span class=variable>old_m</span>) {
    <span class=variable>old_m</span> <span class=operator>=</span> <span class=operator>*</span><span class=variable>m</span>;
    <span class=operator>*</span><span class=variable>m</span> <span class=operator>=</span> <span class=number>1</span>;
  }
}

<span class=type>void</span> <span class=function>unlock</span>(<span class=keyword>volatile</span> <span class=type>int</span><span class=operator>*</span> <span class=variable>m</span>) {
  <span class=operator>*</span><span class=variable>m</span> <span class=operator>=</span> <span class=number>0</span>;
}

<span class=type>void</span> <span class=function>do_stuff</span>(<span class=keyword>volatile</span> <span class=type>int</span><span class=operator>*</span> <span class=variable>m</span>, <span class=type>int</span><span class=operator>*</span> <span class=variable>shared</span>) {
  <span class=function>lock</span>(<span class=variable>m</span>);
  <span class=operator>*</span><span class=variable>shared</span> <span class=operator>=</span> <span class=number>5</span>;
  <span class=function>unlock</span>(<span class=variable>m</span>);
}</code></pre><p>Then, because the store to <code>shared</code> is non-volatile, it is not considered an observable side-effect, and can be freely reordered outside the critical section. You may then think "oh I'll just make everything inside the critical region volatile too!" but (1) this severely limits the optimizations compilers can do (those babies <em>love</em> to reorder stuff) and (2) that's not even the whole problem here. Remember, <code>volatile</code> only gives guarantees about single-threaded programs, and says nothing about multi-threaded ones.</p><details><summary>Note about the race condition:</summary> The swap of <code>old_m</code> and <code>*m</code> in the while loop isn't atomic, so an appropriate interleaving of two threads calling <code>lock()</code> could both enter the critical section, breaking mutual exclusion.</details><h2><a aria-hidden=true class=anchor href=#data-races id=heading-data-races></a>Data Races</h2><p>What is a data race? Since we're using C, we can check <a href=https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf>the C standard</a>, section 5.1.2.4. Paragraph 4 says (emphasis theirs):</p><blockquote><p>Two expression evaluations <em>conflict</em> if one of them modifies a memory location and the other one reads or modifies the same memory location.</p></blockquote><p>and Paragraph 35 says:</p><blockquote><p>The execution of a program contains a <em>data race</em> if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.</p></blockquote><p>(where "before" is precisely defined in the other paragraphs).</p><p>With this in mind, let's take a look at my first attempt at the ticket lock implementation:</p><pre class=syntax-highlighting><code class=language-c><span class=keyword>#include</span> <span class=string>&lt;stdatomic.h></span>

<span class=keyword>typedef</span> <span class=keyword>struct</span> {
  <span class=type>atomic_int</span> <span class=property>next_ticket</span>;
  <span class=type>int</span> <span class=property>now_serving</span>;
} <span class=type>mutex_t</span>;

<span class=type>void</span> <span class=function>lock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=type>int</span> <span class=variable>ticket</span> <span class=operator>=</span> <span class=function>atomic_fetch_add_explicit</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>next_ticket</span>, <span class=number>1</span>, <span class=variable>memory_order_relaxed</span>);
  <span class=keyword>while</span> (<span class=variable>ticket</span> <span class=operator>!=</span> <span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>) {}
}

<span class=type>void</span> <span class=function>unlock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=operator>++</span><span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>;
}</code></pre><p>Assume all uses of this API are correct (i.e. <code>lock()</code> always called before <code>unlock()</code>, no double-locking, and no double-unlocking within a thread). Can you spot the data race?</p><details><summary>(click for answer)</summary> That's right! <code>m->now_serving</code> is written to in <code>unlock()</code> and read from in <code>lock()</code> at the same time, and neither operation is atomic. If we assume that the compiler is not that smart and diligently translates our code into the assembly in the way we'd expect, then this works out fine: write before read means you see the write immediately, read before write is fine since we'll get it on the next iteration. But the compiler reasons that in the while loop, the value either <em>can</em> change (data race UB, since the loop is empty and there are no atomic updates in our program), or the value never changes (infinite loop UB. yes, certain non-terminating programs are UB) and can shoot our foot with whatever gun it likes.</details><p>Not to be deterred, I quickly fixed it:</p><pre class=syntax-highlighting><code class=language-c><span class=keyword>#include</span> <span class=string>&lt;stdatomic.h></span>

<span class=keyword>typedef</span> <span class=keyword>struct</span> {
  <span class=type>atomic_int</span> <span class=property>next_ticket</span>;
  <span class=type>atomic_int</span> <span class=property>now_serving</span>;
} <span class=type>mutex_t</span>;

<span class=type>void</span> <span class=function>lock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=type>int</span> <span class=variable>ticket</span> <span class=operator>=</span> <span class=function>atomic_fetch_add_explicit</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>next_ticket</span>, <span class=number>1</span>, <span class=variable>memory_order_relaxed</span>);
  <span class=keyword>while</span> (<span class=variable>ticket</span> <span class=operator>!=</span> <span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>) {}
}

<span class=type>void</span> <span class=function>unlock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=function>atomic_fetch_add_explicit</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>, <span class=number>1</span>, <span class=variable>memory_order_relaxed</span>);
}</code></pre><p>Great! Now that one of the updates is atomic, we've fixed the UB! That means we're done, right?</p><h2><a aria-hidden=true class=anchor href=#memory-ordering id=heading-memory-ordering></a>Memory Ordering</h2><p>Not so fast! So far, we've just been setting the memory ordering parameter to the atomic operations to <code>memory_order_relaxed</code>, since that's the most flexible for the compiler<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> and we only need atomic updates on individual variables to prevent data races.</p><p>However! Just like with <code>volatile</code>, C compilers are very aggressive about inlining functions and reordering load and stores, and since <code>memory_order_relaxed</code> doesn't impose any constraints on how the non-atomic loads and stores are sequenced, the compiler is still free to do whatever it wants. We're building a mutex after all, so it doesn't count if all we do is ensure the <code>lock()</code> and <code>unlock()</code> functions are race-free; we also need to make sure operations done in the critical section can't get reordered outside the <code>lock()</code> and <code>unlock()</code> calls.</p><p>Looking at the appropriate <a href=https://en.cppreference.com/w/c/atomic/memory_order>cppreference page</a> for other <code>memory_order</code> options, we see a couple that look interesting:</p><blockquote><p><code>memory_order_acquire</code>: A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread (see <a href=https://en.cppreference.com/w/c/atomic/memory_order#Release-Acquire_ordering>Release-Acquire</a> ordering)</p></blockquote><blockquote><p><code>memory_order_release</code>: A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable (see <a href=https://en.cppreference.com/w/c/atomic/memory_order#Release-Acquire_ordering>Release-Acquire</a> ordering)</p></blockquote><p>Hey wait a minute, those names are familiar! "Acquire" is a fancy name for <code>lock()</code> and "release" is a fancy name for <code>unlock()</code>. These are memory orderings <em>specifically made for mutexes</em>; "reads or writes in the current threads" applies to <em>all</em> reads or writes, not just <code>volatile</code> ones. <code>memory_order_acquire</code> makes it so nothing in the critical section can be ordered before a <code>lock()</code>, and <code>memory_order_release</code> makes it so nothing in the critical section can be ordered after an <code>unlock()</code>. Perfect! With this in mind, we can write our final, correct<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup> ticket lock implementation:</p><pre class=syntax-highlighting><code class=language-c><span class=keyword>#include</span> <span class=string>&lt;stdatomic.h></span>

<span class=keyword>typedef</span> <span class=keyword>struct</span> {
  <span class=type>atomic_int</span> <span class=property>next_ticket</span>;
  <span class=type>atomic_int</span> <span class=property>now_serving</span>;
} <span class=type>mutex_t</span>;

<span class=type>void</span> <span class=function>lock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=type>int</span> <span class=variable>ticket</span> <span class=operator>=</span> <span class=function>atomic_fetch_add_explicit</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>next_ticket</span>, <span class=number>1</span>, <span class=variable>memory_order_relaxed</span>);
  <span class=keyword>while</span> (<span class=variable>ticket</span> <span class=operator>!=</span> <span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>) {}
  <span class=function>atomic_thread_fence</span>(<span class=variable>memory_order_acquire</span>);
}

<span class=type>void</span> <span class=function>unlock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=function>atomic_fetch_add_explicit</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>, <span class=number>1</span>, <span class=variable>memory_order_release</span>);
}</code></pre><p>Memory ordering is a super deep field, and I re-learn a lot of it every time I need to use it. Leaving u with one fun thing before I go, read about the <a href=https://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html>DEC Alpha</a>. It's real cursed :3 <a href=https://en.wikipedia.org/wiki/Consistency_model#Relaxing_read_and_read_to_write_program_orders:_Alpha,_RMO,_and_PowerPC>Wikipedia has more</a> but that previous link is by far the simplest explainer of just how bonkers it is.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>In a bizarre twist of fate, the non-<code>_explicit</code> version of atomic functions all default to <code>memory_order_seqcst</code>, the most strict ordering. The C standard library having safe defaults?? Improbable. <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>Minus certain definitions of correct, such as actually descheduling blocked threads, detecting misuses of the API, detecting deadlocks, etc. <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Fun C++ Quiz!]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2223275/" />
      <id>https://wolfgirl.dev/blog/2223275/</id>
      <published>2023-07-27T01:50:46Z</published>
      <updated>2023-07-27T01:50:46Z</updated>
      <summary><![CDATA[In which I demonstrate some truly bizzare C++ ABI behavior.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost.</p><hr><p>Why doesn't the following code run? (comments represent different files):</p><pre class=syntax-highlighting><code class=language-c++><span class=comment>// foo.hpp</span>
<span class=keyword>template</span> <span class=operator>&lt;</span><span class=keyword>typename</span> <span class=type>T</span><span class=operator>></span>
<span class=keyword>struct</span> <span class=type>Foo</span> {
  <span class=keyword>static</span> <span class=type>void</span> <span class=function>foo</span>(<span class=type>void</span>);
}
<span class=comment>// foo.cpp</span>
<span class=keyword>#include</span> <span class=string>&lt;iostream></span>
<span class=keyword>#include</span> <span class=string>"foo.hpp"</span>
<span class=keyword>template</span> <span class=operator>&lt;></span>
<span class=keyword>struct</span> <span class=type>Foo</span><span class=operator>&lt;</span><span class=type>int</span><span class=operator>></span> {
  <span class=keyword>static</span> <span class=type>void</span> <span class=function>foo</span>() { std::<span class=variable>cout</span> &lt;&lt; <span class=string>"foo\n"</span>; }
}
<span class=comment>// main.cpp</span>
<span class=keyword>#include</span> <span class=string>"foo.hpp"</span>
<span class=type>int</span> <span class=function>main</span>(<span class=type>void</span>) {
  <span class=type>Foo</span><span class=operator>&lt;</span><span class=type>int</span><span class=operator>></span>::<span class=function>foo</span>();
}</code></pre><br><details><summary>Answer</summary> <p>From <a href=https://stackoverflow.com/questions/3989435/why-do-i-get-missing-symbols-for-an-explicit-template-specialization-in-a-static>this SO post</a>. Basically, <code>foo.cpp</code> needs to be</p> <pre class=syntax-highlighting><code class=language-c++><span class=keyword>#include</span> <span class=string>&lt;iostream></span>
<span class=keyword>#include</span> <span class=string>"foo.hpp"</span>
<span class=keyword>template</span> <span class=keyword>struct</span> <span class=type>Foo</span><span class=operator>&lt;</span><span class=type>int</span><span class=operator>></span><span class=variable>;
</span><span class=type>void</span><span class=variable> </span><span class=type>Foo</span><span class=operator>&lt;</span><span class=type>int</span><span class=operator>></span><span class=variable>::</span><span class=function>foo</span><span class=variable>() { std::</span><span class=variable>cout</span><span class=variable> &lt;&lt; </span><span class=string>"foo\n"</span><span class=variable>; }</span></code></pre> <p>because otherwise, member function definitions are secretly <code>inline</code>, and since there is no usage site in the same compilation unit they won't get exported as symbols. Declaring them like this lets them be non-inline, and the symbol can be used in the other compilation unit.</p></details><p>hell language btw.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Exploring Mutexes In C]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/2064554/" />
      <id>https://wolfgirl.dev/blog/2064554/</id>
      <published>2023-07-16T18:33:53Z</published>
      <updated>2023-07-16T18:33:53Z</updated>
      
      <content type="html"><![CDATA[<p>Originally published on Cohost under the title "yeah i got nerd-sniped" as a response to someone who posted under the "#c" tag with a simple spinlock implementation.</p><hr><p>i <em>love</em> talking about mutexes so now i'm going to make that everyone else's problem</p><p>ok so. indeed, that code is a relatively straightforward implementation of mutexes. i could digress into stuff about <code>volatile</code> and memory orderings, but instead, there is something more important we need to talk about before considering if this mutex is "good":</p><h2><a aria-hidden=true class=anchor href=#what-is-a-mutex id=heading-what-is-a-mutex></a>What Is A Mutex?</h2><p>By its name, a mutex is something that provides <strong>Mut</strong>ual <strong>Ex</strong>clusion of a shared resource for concurrent tasks. In plain language:</p><blockquote><p>Only one task can access the thing at a time</p></blockquote><p>Programmers call it "acquiring" or "locking" the mutex when you attempt to access that shared resource, and "releasing" or "unlocking" the mutex when you had ownership of the resource and are giving it away for others to use.</p><p>Besides mutual exclusion, there's another property (not in the name) that's required for something to be a proper mutex:</p><h3><a aria-hidden=true class=anchor href=#progress id=heading-progress></a>Progress</h3><blockquote><p>No matter how many tasks are trying to acquire the mutex, if the resource isn't owned, someone will be able to get it</p></blockquote><p>It's hard to mess this one up; in fact I'd say most buggy mutex implementations skew the other way by not correctly providing mutual exclusion. To give a concrete example of how progress could be violated, consider the following implementation: (for some reason i couldn't put it inside a &lt;details>, sorry)</p><pre class=syntax-highlighting><code class=language-c><span class=keyword>#include</span> <span class=string>&lt;stdatomic.h></span>

<span class=keyword>typedef</span> <span class=keyword>struct</span> {
  <span class=type>queue_t</span> <span class=property>waiters</span>;
  <span class=type>atomic_int</span> <span class=property>owner</span>;
} <span class=type>mutex_t</span>;

<span class=keyword>static</span> <span class=keyword>const</span> <span class=type>int</span> <span class=constant>EMPTY</span> <span class=operator>=</span> <span class=number>0</span>;

<span class=type>void</span> <span class=function>lock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>, <span class=type>int</span> <span class=variable>pid</span>) {
  <span class=keyword>if</span> (!<span class=function>atomic_compare_exchange_strong</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>owner</span>, <span class=operator>&</span><span class=constant>EMPTY</span>, <span class=variable>pid</span>)) {
    <span class=function>push_back</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>q</span>, <span class=variable>pid</span>);
    <span class=function>deschedule</span>(); <span class=comment>// Pauses execution until reschedule(pid) called</span>
  }
  <span class=variable>m</span><span class=operator>-></span><span class=property>owner</span> <span class=operator>=</span> <span class=variable>pid</span>;
}

<span class=type>void</span> <span class=function>unlock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>mutex</span>) {
  <span class=type>int</span> <span class=variable>pid</span> <span class=operator>=</span> <span class=function>pop_front</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>q</span>);
  <span class=keyword>if</span> (<span class=variable>pid</span> <span class=operator>!=</span> <span class=constant>EMPTY</span>) {
    <span class=function>reschedule</span>(<span class=variable>pid</span>);
  } <span class=keyword>else</span> {
    <span class=function>atomic_store</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>owner</span>, <span class=constant>EMPTY</span>);
  }
}</code></pre><summary>Can you spot an error with this? I'll hide the intended solution in case you want to puzzle it out. <details><p>Between the <code>atomic_compare_exchange_strong</code> line and the <code>push_back</code> line, the current owner could release the mutex without noticing that we were trying to put ourselves on the queue. This violates progress because a single task trying to acquire the lock was not able to.</p> <p>There are certainly many other bugs, such as being able to race on <code>m->q</code> if it is not atomic. That is the thing about bugs, more pop up than u intend :)</details></summary><p>Progress and mutual exclusion are basically required for a correct mutex, but this last one is just nice to have:</p><h3><a aria-hidden=true class=anchor href=#bounded-waiting id=heading-bounded-waiting></a>Bounded Waiting</h3><blockquote><p>If a task tries to acquire the mutex, it will eventually be the owner of the resource</p></blockquote><p>This can be strengthened further by putting bounds on how long it will take (a function of the number of competing tasks, proportional to), or weakened to deal with probabilistic algorithms. In any case, it's common for implementations attempting to go for bounded waiting do so at the cost of progress or mutual exclusion (by accident, as was the case in the example). So it is refreshing to see a simple spinlock that won't even try to deal with it.</p><p>However! Bounded waiting is still a very important property, and any non-specialized implementation should have it. Consider the following code:</p><pre class=syntax-highlighting><code class=language-c><span class=type>void</span> <span class=function>mainloop</span>(<span class=type>void</span>) {
  <span class=keyword>while</span> (!<span class=variable>should_stop</span>) {
    <span class=function>lock</span>(<span class=operator>&</span><span class=variable>m</span>);
    <span class=function>process</span>(<span class=operator>&</span><span class=variable>shared_resource</span>);
    <span class=function>unlock</span>(<span class=operator>&</span><span class=variable>m</span>);
  }
}</code></pre><p>To be clear, this is is a terrible, effectively single-threaded design. Still, it's not all that uncommon in real designs for threads to relatively quickly acquire and release the same mutex over and over, which can starve other threads if there isn't bounded waiting on the mutex.</p><h2><a aria-hidden=true class=anchor href=#can-we-achieve-bounded-waiting-with-only-stdatomich id=heading-can-we-achieve-bounded-waiting-with-only-stdatomich></a>Can We Achieve Bounded Waiting With Only <code>&lt;stdatomic.h></code>?</h2><p>My claim is yes! Here is an example of such a mutex, known as a ticket lock:</p><pre class=syntax-highlighting><code class=language-c><span class=keyword>#include</span> <span class=string>&lt;stdatomic.h></span>

<span class=keyword>typedef</span> <span class=keyword>struct</span> {
  <span class=type>atomic_int</span> <span class=property>next_ticket</span>;
  <span class=type>atomic_int</span> <span class=property>now_serving</span>;
} <span class=type>mutex_t</span>;

<span class=type>void</span> <span class=function>lock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=comment>// Get our ticket</span>
  <span class=type>int</span> <span class=variable>ticket</span> <span class=operator>=</span> <span class=function>atomic_fetch_add</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>next_ticket</span>, <span class=number>1</span>, <span class=variable>memory_order_relaxed</span>);
  <span class=comment>// Spin until we are being served</span>
  <span class=keyword>while</span> (<span class=variable>ticket</span> <span class=operator>!=</span> <span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>) {}
  <span class=function>atomic_thread_fence</span>(<span class=variable>memory_order_acquire</span>);
}

<span class=type>void</span> <span class=function>unlock</span>(<span class=type>mutex_t</span> <span class=operator>*</span><span class=variable>m</span>) {
  <span class=comment>// Let the next ticket be served</span>
  <span class=function>atomic_fetch_add</span>(<span class=operator>&</span><span class=variable>m</span><span class=operator>-></span><span class=property>now_serving</span>, <span class=number>1</span>, <span class=variable>memory_order_release</span>);
}</code></pre><p>How it works is fairly simple, and pretty much entirely explained in the comments already. It's inspired by how you'd wait in line at a deli or pharmacy.</p><p>Some great things about this lock:</p><ul><li>just one atomic operation to lock and unlock</li><li>small (8 bytes)</li></ul><p>Some not-great things about this lock:</p><ul><li>spin waiting :(</li><li>technically, <a href=https://en.wikipedia.org/wiki/ABA_problem>ABA</a>, but with 32-bit tickets this shouldn't be an issue in practice</li><li>doesn't detect misuse</li></ul><p>Most of these deficits could be solved by better design, but i am too lazy rn :P</p><p>Now, you may have still some questions like "wait why didn't you use <code>volatile</code>?" and "what's with that memory order stuff?", but this post is a little long as it stands, so I figure I'll leave the discussion about How Your Compiler And CPU Lie To You for a future post. Or just read <a href=https://faultlore.com/blah/tower-of-weakenings/>Tower Of Weakenings</a> because it answers or has links to answers for those questions and more.</p><p><del>And by the way, it's entirely possible I missed something and this code has a bug I am unaware of, so please do ask questions if you think something is up!</del><br><del>edit: I did get it wrong in multiple ways the first time. After talking with a few friends I am now more confident it is correct :) will explain in the later post</del><br>edit 2: I got it wrong again! Definitely making a post because explaining how I got things wrong is quite a journey.</p>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[I'm trans :)]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/759538/" />
      <id>https://wolfgirl.dev/blog/759538/</id>
      <published>2023-01-01T05:16:31Z</published>
      <updated>2023-01-01T05:16:31Z</updated>
      <summary><![CDATA[My official coming-out post! In which I take "new year new me" to a whole 'nother level.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost under the title "So That Last One Deserves Some Explanation Huh", as a follow-on to a News Years Resolution post where one of my resolutions was to "trans my gender".</p><hr><p>so um you may know about <a href=/blog/679878/>my previous post where i explored the trans question</a>, and TL;DR it came down to "PolyWolf is and has always been a girl and also I've enjoyed 'acting' as her" vs "well but like i don't want to actually like <em>be</em> a girl haha that would be crazy".</p><p>This situation has predictably resolved itself in favor of the former option. In fact, I have new and improved refutations for all the reasons I thought I may have not been trans:</p><h4><a aria-hidden=true class=anchor href=#i-didnt-think-about-this-when-i-was-younger id=heading-i-didnt-think-about-this-when-i-was-younger></a>"I didn't think about this when I was younger"</h4><p>Turns out it's not a universal trans experience to have dysphoria as a child? These are the cases I heard about in media and from the 1 irl trans friend I know so it's understandable how I was confused on this point.</p><p>It's possible (but maybe still a stretch?) to recontextualize some of the "mild body image issues" I had before thinking about trans as weak signals. Mostly weird feelings about receeding hairline and body hair but again I figured these could be something related more to toxic masculinity and beauty standards.</p><h4><a aria-hidden=true class=anchor href=#femme-presentation-doesnt-appeal-to-me id=heading-femme-presentation-doesnt-appeal-to-me></a>"Femme presentation doesn't appeal to me"</h4><p>Doesn't need to. Turns out it's possible to be a girl and not want to wear dresses and makeup wow shocking. It's almost like some cis girls have those desires too, crazy right (these sentences r dripping with sarcasm directed at my previous self).</p><h4><a aria-hidden=true class=anchor href=#male-body-has-advantages-athletically id=heading-male-body-has-advantages-athletically></a>"Male body has advantages athletically"</h4><p>I can just. Keep working out. Yes HRT may reduce muscle mass and buildup but like that doesn't change the main appeals of working out for me? I'm fit enough for exercise to be fun rather than a chore, and this aspect of my identity can remain with no issues whatsoever.</p><h4><a aria-hidden=true class=anchor href=#male-body-has-advatages-socially id=heading-male-body-has-advatages-socially></a>"Male body has advatages socially"</h4><p>Yes, it sure does. Irrelevant info as to whether or not I am trans, only relevant so far as to decisions regarding transition.</p><h4><a aria-hidden=true class=anchor href=#i-dont-like-drugs id=heading-i-dont-like-drugs></a>"I don't like drugs"</h4><p>See above, and also consider that drugs aren't necessary for many aspects of transitioning! It'd sure be nice to not be so hesitant about it tho...</p><hr><p>When coming to these conclusions, I took a different strategy than before. Instead if assuming I was cis and trying to prove beyond a shadow of a doubt that I'm trans, I assumed, hypothetically, that I was trans, and because there would be just so many things ahead of me to think about in regards to transition, I should try to come up with reasons why I'm cis becauae that would take a lot off my plate. Cross-checking those reasons from a trans perspective, however, yielded this, so um I guess I'm living that hypothetical now.</p><h2><a aria-hidden=true class=anchor href=#what-comes-next id=heading-what-comes-next></a>What Comes Next?</h2><p>Here's my list in rough order of priority:</p><ul><li>Voice Training (so i can get on VC as PolyWolf)</li><li>Waist Training</li><li>Body Hair Removal (I'd like it gone regardless, probably waxing idk)</li><li>Sperm Freezing</li><li>(Below items probably after I graduate college and move somewhere more permanent)</li><li>Facial Hair Removal (If not trans, less sure about this one. Probably w/ lazers)</li><li>HRT (the kind that gets u big boobs. i want those gazonkers. i am not aware of the downsides)</li></ul><p>It may surprise some that waist training is so high. It's actually something that seems pretty exciting but idk we'll see. Voice training has already started btw but it's tough and will probably take a while before I achieve the voice in my head<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup>.</p><p>Notice some things conspicuously missing:</p><ul><li>New Clothes</li><li>Makeup</li><li>Nails</li><li>Social Transition</li></ul><p>that's right. ima boymode. i actually would like to avoid these things for as long as i can here's hoping</p><p>Anyways, thanks for taking the time to read this, it's a big decision for me to come out semi-privately like this even. Thanks for understanding!!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>that's to say, what my inner monologue voice naturally sounds like, ANOTHER HUGE CLUE HOW COULD I HAVE BEEN SO BLIND <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[Who Is PolyWolf?]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/679878/" />
      <id>https://wolfgirl.dev/blog/679878/</id>
      <published>2022-12-27T17:36:17Z</published>
      <updated>2022-12-27T17:36:17Z</updated>
      <summary><![CDATA[In which I elaborate on various aspects of my identity.]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost. Most notably, just a few days before <a href=/blog/759538/>I figured out I was trans</a>.</p><hr><p>With some more time to just relax over Winter Break, I'm spending a bit of it thinking about personal identity. What I'm hoping is that, by writing some of these thoughts down, I can organize them and get a better understanding. Unfortunately for you, you get to read these thoughts too.</p><p>One interesting thing of note is that I've already posted about this before on my LegalName<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> blog, calling PolyWolf my "online presence". I didn't go into too much more detail than that, so that's what this post will focus on: PolyWolf.</p><h2><a aria-hidden=true class=anchor href=#where-do-i-exist id=heading-where-do-i-exist></a>Where Do I Exist?</h2><p>This seems like a silly question, but is actually one I'm having a bit of trouble answering and whose answer will have ramifications on the answers to other basic questions.</p><h3><a aria-hidden=true class=anchor href=#option-1-polywolf-is-a-concept id=heading-option-1-polywolf-is-a-concept></a>Option 1: PolyWolf is a concept</h3><p>That is to say, PolyWolf only exists as a fictional character. Thus, it's not PolyWolf herself typing these words, but rather the person "playing" PolyWolf in some sense.</p><p>This was the initial conception, but that boundary has been muddied and I'm not sure this option would be true anymore.</p><h3><a aria-hidden=true class=anchor href=#option-2-polywolf-is-a-headmate id=heading-option-2-polywolf-is-a-headmate></a>Option 2: PolyWolf is a headmate</h3><p>I think this is the right systems/plural terminology. This would mean there are both LegalName and PolyWolf thought processes in the same brain, and sometimes PolyWolf is able to have an influence on the real world. This split would likely be due to spending so much time in Option 1. However, I am hesitant to label it this way since I don't think I have quite the same "fronting" experience as other systems I've talked to. It more feels like PolyWolf is just another expression of the same self.</p><h3><a aria-hidden=true class=anchor href=#option-3-i-am-polywolf id=heading-option-3-i-am-polywolf></a>Option 3: I Am PolyWolf</h3><p>That is, "acting like PolyWolf" is just how I actually want to act, unburdened thanks to online anonymity. The case for this is bolstered by the fact that I usually act that way with close friends too.</p><p>Thanks to an interesting combination of the following circumstances:</p><p>a) my Discord username is PolyWolf b) i chose not to use real name in any college Discord server<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup> c) i made most of my college friends via Discord</p><p>I ended up in a situation where I <em>am</em> just known to some people as PolyWolf. It was a <em>very</em> interesting experience to be called that IRL, not sure how to describe it but it was good I think?</p><h3><a aria-hidden=true class=anchor href=#option-4-polywolf-is-a-way-that-i-act id=heading-option-4-polywolf-is-a-way-that-i-act></a>Option 4: PolyWolf is a way that I act</h3><p>It's a bit nuanced in how this is distinct from all the other options. I think this is the best way to describe how PolyWolf is more than just a concept, not a separate consciousness, and not completely who I am.</p><p>Basically, you know how we all act different when we are with different groups? Like with family vs close friends vs other friends vs work vs online? I think I am just very aware of those distinctions<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup> and happened to give a name to some of those.<sup class=footnote-ref><a data-footnote-ref href=#fn-4 id=fnref-4>4</a></sup></p><hr><p>Ok so we've deduced that, at the time of this writing, Option 4 is most likely to describe my current existence. It's just, there's still one snag,,,</p><h2><a aria-hidden=true class=anchor href=#gender id=heading-gender></a>Gender</h2><p>Here's where it gets tricky. Let's go through all the different options, shall we?</p><div class=table-wrapper><table><thead><tr><th>PolyWolf is...</th><th>Gender</th></tr></thead><tbody><tr><td><b>Option 1</b></td><td>girl (she/her)</td></tr><tr><td><b>Option 2</b></td><td>girl (she/her)</td></tr><tr><td><b>Options 3 & 4</b></td><td>???</td></tr></tbody></table></div><p>now i know what ur thinking. this is a very silly table. <em>how could that last box possibly be <code>???</code>?</em></p><p>Well, see, IRL I'm AMAB, and I'm not sure whether I actually want to <em>be</em> a girl physically!</p><h3><a aria-hidden=true class=anchor href=#am-i-trans id=heading-am-i-trans></a>Am I Trans?</h3><p>Arguments against:</p><ul><li>I didn't even think about this when I was younger <ul><li>I've been told this isn't a strong argument; lots of people don't until they start really grasping the effects of puberty/aging/relationships</li></ul></li><li>femme presentation doesn't appeal to me <ul><li>Also a weak argument</li></ul></li><li>I quite enjoy the benefits of a male body; there's a lot going for it athletically and there's less discrimination <ul><li>Sure, but also that last one is <em>very</em> problematic</li></ul></li></ul><p>Arguments for:</p><ul><li>"Would u press a button that would instantly turn u into PolyWolf if there were no negative reprecussions" yes <ul><li>See but the thing is, there are always reprecussions, and transitioning doesn't seem likely to fully realize PolyWolf</li></ul></li><li>"Would u rather like girls in a gay way" yes <ul><li>Although this could just be due to gender norms</li></ul></li><li>something you'll have 2 check my AD for (later, after I post this)</li></ul><p>Extra facts:</p><ul><li>I have a hard "no drugs" stance<sup class=footnote-ref><a data-footnote-ref href=#fn-5 id=fnref-5>5</a></sup>, so HRT is a little scary to think about</li><li>Very mild body image issues "but who doesn't have that" <ul><li>I've been told this is almost too powerful of a rationalization, and reading something recently about how you generally <em>should</em> be somewhat comfortable with yourself in a mirror<sup class=footnote-ref><a data-footnote-ref href=#fn-6 id=fnref-6>6</a></sup></li></ul></li><li>I don't like either of the gender norms</li></ul><p>So basically, I still don't know. The strongest evidence for is the AD stuff and the strongest evidence against is the fact that I still sometimes feel like and enjoy being LegalName who does have guy gender.</p><h3><a aria-hidden=true class=anchor href=#am-i-enby id=heading-am-i-enby></a>Am I Enby?</h3><p>So based on all of the above, with trans potentially not being a good enough fit, the next possibility to consider is being nonbinary. While I actually think this would fit LegalName quite well<sup class=footnote-ref><a data-footnote-ref href=#fn-7 id=fnref-7>7</a></sup>, it doesn't really feel right for PolyWolf, ya know? There's that silly table, after all.</p><p>Still, maybe this is the best way to describe Option 4, where I "act like a girl" sometimes and like a guy other times. Or maybe I really am just trans and in denial :p</p><hr><p>Anyways that's all for now, let me know in the comments if any of this resonates with you, I'd love to hear about it and know that I'm not alone in this. Also thanks to @lft and @Gabriella439 and two anonymous friends for helping proofread this and provide preliminary feedback, this piece would be much worse without it.</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Using LegalName as a substitute for any time I would use my actual legal name, because I try to have good opsec barriers between that name and PolyWolf <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>Until recently, again for (a little silly in retrospect) opsec reasons <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>Maybe it's the autism? Who knows! <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a></p></li><li id=fn-4><p>As an aside, I feel a little sad when I can't reveal myself as PolyWolf to the groups where I'm only known as LegalName, so take from that what you will in the following sections. <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-4>↩</a></p></li><li id=fn-5><p>For no particular reason tbh. It's just something I feel more comfortable doing. Not wanting tatoos/piercings ever for the same reasons. <a aria-label="Back to reference 5" class=footnote-backref data-footnote-backref data-footnote-backref-idx=5 href=#fnref-5>↩</a></p></li><li id=fn-6><p>Which, I'm not sure whether I am or not. I probably dissociate by not looking at my face and body hair sucks but again these could also just be normal things idk <a aria-label="Back to reference 6" class=footnote-backref data-footnote-backref data-footnote-backref-idx=6 href=#fnref-6>↩</a></p></li><li id=fn-7><p>Gender norms stinky! I could just be myself and still use he/him but also not "be a guy" and that would be cool and also invisible <a aria-label="Back to reference 7" class=footnote-backref data-footnote-backref data-footnote-backref-idx=7 href=#fnref-7>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
    <entry>
      <title><![CDATA[English Is A Goddamn Nightmare Language]]></title>
      <link rel="alternate" href="https://wolfgirl.dev/blog/630335/" />
      <id>https://wolfgirl.dev/blog/630335/</id>
      <published>2022-12-19T09:12:11Z</published>
      <updated>2022-12-19T09:12:11Z</updated>
      <summary><![CDATA[In which I rationalize a grammatical brainfart using lingustic techniques]]></summary>
      <content type="html"><![CDATA[<p>Originally published on Cohost with no title. The title I chose came from a tag that autocompleted when I used the "#english language" tag.</p><hr><p>today, when trying to express</p><blockquote><p>i need to use the bathroom</p></blockquote><p>enough brain quirks fired at once to turn it into the monstrosity</p><blockquote><p>i got needing bathroom using</p></blockquote><p>sometimes i astound myself with my own eloquence. in this essay, i will examine the resonable (and unreasonable) transformations you could use to purposefully construct it</p><hr><p>oh? you thought this would be a joke? you thought i wouldn't actually write an essay? this is cohost i can do what i want</p><p>(this content was produced in collaboration with an anonymous linguist friend)</p><h3><a aria-hidden=true class=anchor href=#1-nounify-the-verb id=heading-1-nounify-the-verb></a>1) Nounify the verb</h3><p>It's common knowledge that, in English, you can verbify<sup class=footnote-ref><a data-footnote-ref href=#fn-1 id=fnref-1>1</a></sup> most nouns (to varying degrees of success). For some verbs, you can also go backwards, forming a <a href=https://en.m.wikipedia.org/wiki/Gerund>gerund</a> (verb conjugation that acts like a noun). I bring that up to confuse you, as we are not actually forming a gerund yet, just going back to a related noun "need" from the verb "to need"<sup class=footnote-ref><a data-footnote-ref href=#fn-2 id=fnref-2>2</a></sup> Our sentence is now</p><blockquote><p>i have the need to use the bathroom</p></blockquote><h3><a aria-hidden=true class=anchor href=#2-change-the-tense-of-to-use id=heading-2-change-the-tense-of-to-use></a>2) Change the tense of "to use"</h3><p>For some reason, we can change the infinitive "to use" into a <a href=https://en.m.wikipedia.org/wiki/Uses_of_English_verb_forms#Perfect_and_progressive_nonfinite_constructions>Progressive Infinitive</a>, "to be using". This makes the sentence more complex, but I think it adds clarity about when the action will happen (in the future).</p><blockquote><p>i have the need to be using the bathroom</p></blockquote><h3><a aria-hidden=true class=anchor href=#3-make-an-object-gerund id=heading-3-make-an-object-gerund></a>3) Make an object gerund</h3><p>Recall that a gerund is a conjugation of verb that acts like a noun. We have one of these in our sentence: "using". Because it's being applied to an object, "bathroom", it can make a special form of compound noun known<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3>3</a></sup> as an noun-gerund compound, "bathroom using". This transformation golfs a word and adds a little flair to an otherwise standard phrasing. A hypen may or may not be required, I am choosing not to add one for reasons<sup class=footnote-ref><a data-footnote-ref href=#fn-3 id=fnref-3-2>3</a></sup></p><blockquote><p>i have the need to be bathroom using</p></blockquote><h3><a aria-hidden=true class=anchor href=#4-fully-gerundify-need id=heading-4-fully-gerundify-need></a>4) Fully gerundify "need"</h3><p>Earlier, we didn't make "to need" a gerund. Let's change that now, substituting the noun "need" for the gerund "a needing"</p><blockquote><p>i have a needing to be bathroom using</p></blockquote><h3><a aria-hidden=true class=anchor href=#5-informalize-have id=heading-5-informalize-have></a>5) Informalize "have"</h3><p>A classic, if you've ever been around native English speakers. You can just replace "have" with "got" when it's referring to possesion and people will get it! Makes you sound cool imo, like you know you're breaking a rule but because you understand the intent it's a flourish.</p><blockquote><p>i got a needing to be bathroom using</p></blockquote><h3><a aria-hidden=true class=anchor href=#6-simplify-out-a id=heading-6-simplify-out-a></a>6) Simplify out "a"</h3><p>Another informal transformation, definitely approaching the limits of modern slang by doing this. A more standard version is the one applied to get rid of "some" in the plural version: compare "i got some cats" -> "i got cats" to "i got a cat" -> "i got cat"</p><blockquote><p>i got needing to be bathroom using</p></blockquote><h3><a aria-hidden=true class=anchor href=#7-simplify-out-to-be id=heading-7-simplify-out-to-be></a>7) Simplify out "to be"</h3><p>No standard English rules let us do this actually. However, because it's English, we can also just make up rules on the fly and if sounds ok then it works.</p><p>One way of reasoning about this is that because "to be" is connecting "needing" (verb in gerund form that we are going to treat like a verb) with "bathroom using" (noun gerund compound), the latter counts as an object for the verb, and we can simplify the progressive tense back out. Simpler analouge is that "i need to be eating" can be simplified to "i need eating" with <strong><em>roughly</em></strong> the same message coming across.</p><p>Another way to view this is creating an illegal gerund noun gerund compound that doesn't exist anywhere else<sup class=footnote-ref><a data-footnote-ref href=#fn-4 id=fnref-4>4</a></sup> in English, out of existing words in the sentence, that is now being possessed by "got". There is less rationale for this other than i think it sounds cool.</p><p>And that, my friends, is how we end up with the beautiful phrase,</p><blockquote><p>i got needing bathroom using</p></blockquote><p>As my linguist friend points out, this is just one of many ways you can get to this end sentence. We just plotted out this path as the most likely to fit with our mental models. (There's also a path where you can accidentally turn "have" used as an auxiliary verb into "got" but that doesn't make any sense and also this post is too long already)</p><p>anyways thank you for coming to my TED talk</p><hr><p>Also big thank-you to my linguistics friend who helped me fact-check and review this post! Ur the best bestie!!</p><section class=footnotes data-footnotes><ol><li id=fn-1><p>Like just there! I did it again! <a aria-label="Back to reference 1" class=footnote-backref data-footnote-backref data-footnote-backref-idx=1 href=#fnref-1>↩</a></p></li><li id=fn-2><p>English is a hell language yes <a aria-label="Back to reference 2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=2 href=#fnref-2>↩</a></p></li><li id=fn-3><p>To whom, exactly? As it turns out, there are <a href=https://english.stackexchange.com/questions/361283/hyphen-in-noun-gerund-compounds>style guides for this!</a> <a aria-label="Back to reference 3" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3 href=#fnref-3>↩</a> <a aria-label="Back to reference 3-2" class=footnote-backref data-footnote-backref data-footnote-backref-idx=3-2 href=#fnref-3-2>↩<sup class=footnote-ref>2</sup></a></p></li><li id=fn-4><p>WELL I SURE HOPE IT DOESN'T OK? <a aria-label="Back to reference 4" class=footnote-backref data-footnote-backref data-footnote-backref-idx=4 href=#fnref-4>↩</a></p></li></ol></section>]]></content>
      <author>
        <name>PolyWolf</name>
        <uri>https://wolf.girl.technology/</uri>
      </author>
    </entry>
  
  </feed>
