# rss_item

guid: https://willhbr.net/2024/12/16/going-gigabit/
title: Going Gigabit
url: https://willhbr.net/2024/12/16/going-gigabit/
feedurl: https://willhbr.net/feed.xml
content:
<p>I’ve finally got a network and internet connection fit for the 2020s. My internet connection is gigabit, and my wifi router has been replaced with one that can actually make use of it.</p>

<p>Firstly, the internet. Put simply, in Australia the internet situation is bad. Up until recently the fastest home internet plan was 100Mb/s. When faster plans were introduced they were significantly more expensive. I’m not well versed on the history but <a href="https://en.wikipedia.org/wiki/Internet_in_Australia">Wikipedia sums it up well</a>:</p>

<blockquote>
  <p>The national broadband network aimed to provide up to 1000 Mbit/s speeds […] This has since been revised […] for 25 Mbit/s to 100 Mbit/s</p>
</blockquote>

<p class="caption">Although 25Mb/s <strong>is</strong> “up to 1000Mb/s” if you think about it.</p>

<p>Anyway, for boring reasons I was with <a href="https://en.wikipedia.org/wiki/Internode_(ISP)"><em>Internode</em></a> and had managed to get a secret deal for 250Mb/s at $90 per month after calling them up asking to cancel my service. Last month they sent me an email saying they were increasing the price to $100. I reminded them of their promise that the secret deal was permanent and that they weren’t going to increase the price, but they weren’t interested in negotiating.</p>

<p>Being too stubborn to go back to a 100Mb/s plan, I looked around for other options and found that for $99 I could get a gigabit plan from <a href="https://www.canstarblue.com.au/internet/aussie-broadband-launches-buddy-telco/"><em>Buddy Telco</em></a>—over three times faster than the $100 <em>Internode</em> plan. I cancelled my existing plan (at the end of the billing cycle to not waste any money on internet I’d already paid for) and signed up for a new gigabit connection.</p>

<p><em>Buddy Telco</em> is an offshoot of the well-regarded <em>Aussie Broadband</em> ISP, but saving costs by not having phone support. I was a little concerned that the setup would be painful, as when I’ve moved or swapped routers I’ve had to call tech support and either have them tell me some setting I needed to toggle on the router (it’s always vlan tagging) or find out that everything on my end is correct and they just need to press a button on their end. That’s always the most frustrating end to an evening of debugging. At this point if I can’t find the problem in a few minutes of tinkering I’ll just call up customer support, wait on hold while I tinker, and then follow along with whatever their script wants me to do. Reboot the router? Reboot the fridge? I don’t think that’ll do anything but I’ll do it anyway.</p>

<p>My new connection activated last week, I expected to get sent <a href="https://en.wikipedia.org/wiki/Point-to-Point_Protocol_over_Ethernet">PPPoE</a> credentials and some other instructions, but it just said “Please connect to UNI-D port 2, on your NBN device.” I moved the WAN cable from port 1 to port 2, and it worked. No credentials, no vlan tagging, no calling customer support.</p>

<p>Their online portal <em>does</em> have some self-service debugging tools. You can check your connection status, reset the connection, and things like that. They have some basic descriptions to say “do this if you’ve changed your router” and such, so I’m hopeful that the lack of customer support won’t end up being an issue.</p>

<p>I checked Grafana, and there we are: 911Mb/s. I guess that’s gigabit.</p>

<p><img src="/images/2024/gigabit-graph.webp" alt="Graph from Grafana showing connection speed increase from 269Mb/s to 911Mb/s" /></p>

<p>I’ve been monitoring my internet speed <a href="/2023/07/16/simple-home-server-monitoring-with-prometheus-in-podman/">since I setup Prometheus</a>. After a few days I noticed my new connection will periodically drop from ~900Mb/s to 400Mb/s. I wasn’t sure if this is an issue with the connection, or my server that runs the speed test. It’s running on my less powerful “production” server, a <a href="https://blog.codinghorror.com/the-scooter-computer/">“scooter computer”</a> that I bought in 2017 with cheap 2015-ish era hardware. To narrow it down, I ran a parallel speed test on my (much newer) NUC. This seems to have fewer slowdowns, but it’s hard to draw a definitive conclusion with only a few days of data, and a lot of the other changes to my network would confuse this result.</p>

<p>Speaking of hardware, my quest for gigabit also led me to finally take my home networking hardware seriously.</p>

<p>Up until now, I’ve been using the free router that my ISP gave me almost 7 years ago. It’s a <a href="https://www.tp-link.com/au/service-provider/xdsl/archer-vr1600v/">TP-Link Archer VR1600v</a>, one of the handful of routers that Australian ISPs would give out<sup id="fnref:no-routers" role="doc-noteref"><a href="#fn:no-routers" class="footnote" rel="footnote">1</a></sup> and it’s somewhat <a href="https://www.markhansen.co.nz/doubled-ping/">notoriously bad</a>. It’s never seen a software update; which is not what you want from the device between you and the rest of the internet.</p>

<p>Without a fast internet connection, buying a “real” router just seemed like a waste. As I accumulated more home servers (like <a href="/2023/07/03/picking-a-synology/">my Synology</a>), instead of being an adult and buying a switch to chain multiple devices off the poorly-placed Ethernet ports in my apartment, I used another free ISP router in bridge mode to do the job.<sup id="fnref:positioning" role="doc-noteref"><a href="#fn:positioning" class="footnote" rel="footnote">2</a></sup></p>

<p>My home network looked like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> o TP-Link Archer VR1600v Router
 ├─ Wifi devices
 ├─ Steve (Scooter computer)
 ╰─o Technicolor TG789vac v2 Router (Bridge mode)
   ├─ Synology DS420j
   ├─ Brett (Intel NUC)
   ╰─o─ Thinkpad thunderbolt dock
     ╰─ MacBook Air M1 (sometimes)
</code></pre></div></div>

<p>It worked and didn’t require buying any new hardware. I was optimistic about the capability of my Wifi 5 (ie 802.11ac) router, since the maximum speed of an 802.11/ac connection is a bit over a gigabit. Sadly this was not the case, it capped out at about 310Mb/s.</p>

<p>Knowing that my wifi router was likely to be the biggest bottleneck in my home network, I wanted to check that there weren’t any other bottlenecks lurking. It would be especially bad if the wiring in my apartment was a limiting factor, since that would be expensive to replace.</p>

<p>I started by generating a 1 gigabyte file using <code class="language-plaintext highlighter-rouge">head -c 1G /dev/random &gt; testfile</code>, and then starting a webserver with <code class="language-plaintext highlighter-rouge">python -m http.server</code> to see how fast I could download it—and what the download speed the browser reported. I ran this on my dev machine—Brett—and that capped out at 80Mb/s. Shouldn’t this be at least as fast as accessing the internet?</p>

<p>Maybe the Python webserver isn’t able to push data out fast enough? Ideally we’d want a server that is designed to push out data as fast as possible. This is where <a href="https://openspeedtest.com"><em>OpenSpeedTest</em></a> comes in. You can run it in a container and run a speed test across your local network:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>podman run <span class="nt">-d</span> <span class="nt">-p</span> 3000:3000 <span class="nt">-p</span> 3001:3001 docker.io/openspeedtest/latest
</code></pre></div></div>

<p>You just click the button in the web UI, and you get a speed test.</p>

<p>I tested from my laptop (over wifi) to both Steve (scooter) and Brett (NUC). Steve got the speeds I would expect: 298 down, 345 up. Brett gets 92 down and 364 up. It seems like Python wasn’t the problem after all. The only difference in the two servers is Brett is going through the bridging router and Steve is directly connected to my wifi router. I plugged Brett directly into the main router and got 315Mb/s in both directions.</p>

<p>It’s not clear why exactly the bridge-mode router can accept traffic at over 300Mb/s<sup id="fnref:faster-not-wifi" role="doc-noteref"><a href="#fn:faster-not-wifi" class="footnote" rel="footnote">3</a></sup> but can’t sent traffic that fast back in the other direction. Either way, it was a bottleneck and had to go. A quick trip to the shop and it was replaced with a TP-Link 5-port gigabit switch. Now both Steve and Brett have equally fast connections.</p>

<p>I feel a bit silly not having tested this earlier, because my Synology was also sitting behind the bridge-mode router, and so this would have also been slowing down transfers there. Lesson learnt, I guess. Now the limiting factor will be the wifi, rather than me being too lazy to spend $34 on a switch.</p>

<p>The final puzzle piece is wifi. So far all my tests from my laptop have been wireless, and so the limiting factor has been the crappy old VR1600v.</p>

<p>I consulted with a colleague who helped me navigate the complex world of wifi routers. I realised that I’d never actually bought a router before, there was always just one available somewhere. The two important things I learnt is that you can get a wifi 6 router for about $100, but you might not get your full gigabit out of that. For an extra $50 you can push the maximum speed up an extra gigabit.</p>

<p>The router I ended up getting was the <a href="https://www.tp-link.com/au/home-networking/wifi-router/archer-ax53/">TP-Link A53</a>, largely dictated by what was in stock in shops a walking distance from home. It’s exactly the boring appliance I was hoping for; it’s router shaped, doesn’t require you to download an app, has a handful of ports, and gives my laptop fast internet.</p>

<p>It was tempting to get something super fast, but since none of my devices have faster than gigabit ethernet, having wifi that’s substantially faster than my wired networking seemed like a waste. If I make the jump to 2.5 or 10Gb/s devices, I’ll replace the router with something more capable, but I don’t think that’s likely anytime soon.</p>

<p>Is it worth upgrading your home network? If you’re in Australia and have fibre coming into your house or apartment, go get a gigabit plan and a router to match. If you’ve got a slower connection but have some kind of networked storage? Get a better router and switch, I should have done this years ago. Will this make a meaningful difference in my real-world internet speed? Probably not, everything is hosted in the US or Europe anyway and the speed of light isn’t getting any faster.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:no-routers" role="doc-endnote">
      <p>This seems to be much less common now. <a href="#fnref:no-routers" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:positioning" role="doc-endnote">
      <p>To clarify, the router and scooter computer are in the cupboard where the fibre connection terminates, which also has a small ethernet patch panel for the other ports in the apartment. The NUC and Synology sit next to my desk, since they’ve got fans and appreciate having a bit more airflow. There’s only one ethernet port there<sup id="fnref:port-placement" role="doc-noteref"><a href="#fn:port-placement" class="footnote" rel="footnote">4</a></sup> so it has to get split between multiple devices. <a href="#fnref:positioning" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:faster-not-wifi" role="doc-endnote">
      <p>Presumably it would be able to go even faster if I wasn’t limited by the speed of my wifi. <a href="#fnref:faster-not-wifi" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:port-placement" role="doc-endnote">
      <p>I want to have a serious word with whoever decided the ethernet port placement in this apartment. There are more ports by my bed than there are at my desk. Are they expecting me to use an alarm clock with wired networking? <a href="#fnref:port-placement" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>


<a href="https://willhbr.net/2024/12/16/going-gigabit/">Permalink</a>
&bull; December 16, 2024
&bull; Send feedback via
  <a href="https://ruby.social/@willhbr" target=_blank>mastodon</a> or
  
  <a href="mailto:feedback@willhbr.net?subject=Feedback:%20Going%20Gigabit" target=_blank>email</a>.


guid: https://willhbr.net/2024/12/27/solving-sudoku-with-tmux/
title: Solving Sudoku with tmux
url: https://willhbr.net/2024/12/27/solving-sudoku-with-tmux/
feedurl: https://willhbr.net/feed.xml
content:
<p>The question that everyone has been asking me since I <a href="/2024/03/15/making-a-compiler-to-prove-tmux-is-turing-complete/">compiled Python to run on tmux</a> is: “can you actually do anything useful with this?”. I’m happy to report back that the answer is still no, but I can now use tmux to solve sudoku, and I can do it using a different and trickier approach than the one I used with the compiler.</p>

<p>With projects like this, seeing is believing, so I made a <a href="https://youtu.be/Tz74vs_nH7M">quick video showing what it looks like</a>:</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
<iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" width="650" height="400" src="https://www.youtube.com/embed/Tz74vs_nH7M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
</div>

<p>If you want to just check out the code for yourself, you can see the code <a href="https://github.com/willhbr/tmux-sudoku">on GitHub</a> but the real joy is understanding in great detail how this works.</p>

<p>I got the idea for this project after seeing <a href="https://github.com/konstin/sudoku-in-python-packaging"><em>konsti</em> write a sudoku solver using Python dependency resolution</a>. This seemed like an interesting area to apply my (very weird) expertise. It’s also much more useful than the <code class="language-plaintext highlighter-rouge">is_prime</code> function that I ran on tmux before.</p>

<p>My Python-tmux (and Brainfuck-tmux) compiler worked using <a href="https://www.man7.org/linux/man-pages/man1/tmux.1.html#HOOKS">tmux hooks</a>. You can activate a hook when a client navigates to a particular window, and that hook can then run any tmux action, like create a new window or split the window into separate panes. You can run multiple actions on one hook, and by having the last action navigate to the next window, you can treat tmux windows and their hooks as instructions in a program. Control flow is implemented by jumping forwards or backwards over multiple windows, depending on the value of a variable.</p>

<p>Since I’d made the compiler, I knew it was <em>possible</em> to solve a sudoku, but my compiler doesn’t support arrays, strings, or scoped/non-global variables, so it would either be incredibly painful, require changes to the compiler, or both.</p>

<p>Instead, I wanted to think of a different approach. After a lot of deep thinking and head-scratching, I have created a short tmux program that can solve a sudoku using just two <code class="language-plaintext highlighter-rouge">bind-key</code> statements.</p>

<p>The sudoku is represented as up to 81 tmux user-defined options—prefixed with an <code class="language-plaintext highlighter-rouge">@</code>:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span> -<span class="n">g</span> @<span class="n">x2y0</span> <span class="s1">'4'</span>
<span class="n">set</span> -<span class="n">g</span> @<span class="n">x4y0</span> <span class="s1">'5'</span>
<span class="n">set</span> -<span class="n">g</span> @<span class="n">x0y1</span> <span class="s1">'9'</span>
<span class="n">set</span> -<span class="n">g</span> @<span class="n">x3y1</span> <span class="s1">'7'</span>
<span class="n">set</span> -<span class="n">g</span> @<span class="n">x4y1</span> <span class="s1">'3'</span>
<span class="n">set</span> -<span class="n">g</span> @<span class="n">x5y1</span> <span class="s1">'4'</span>
<span class="n">set</span> -<span class="n">g</span> @<span class="n">x6y1</span> <span class="s1">'6'</span>
...
</code></pre></div></div>

<p>Since this is a bit tedious to write out by hand, I wrote a script that turns a more human-editable grid into the <code class="language-plaintext highlighter-rouge">set</code> commands. You could implement this in tmux itself, it would just be a bit of a faff, and we’re here for sudoku solving, not string processing.</p>

<p>The second part of this pre-processing step is to make an “array” containing the indices of the blank squares in the sudoku.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span> -<span class="n">g</span> @<span class="n">blanks</span> <span class="s1">'0010305060708011217181021232620333538304243444'</span>
</code></pre></div></div>

<p>Every two characters is an xy coordinate of where there is a blank cell that needs to be filled in. Like loading the human-readable sudoku into the coordinate-based variables, this could be done in tmux but for simplicity my script outputs this as well.</p>

<p>The key part of my sudoku solver is the ability to quickly check whether the sudoku is solved. Since we’re brute-forcing the solution, we’ll have to check <em>a lot</em> of possible solutions, so we want this to be fast. An advantage of writing the tmux config file manually (as opposed to compiling “Python”), is that we can basically write a single “instruction” to do this check in one go. That way we’re spending much less computation time in the cursed tmux “VM”.</p>

<p>We can use variable expansion to build a string with the contents of each row, column, and 3x3 cell. For example to get the first row:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#{@x0y0}#{x1y0}#{x2y0}#{x3y0}#{x4y0}#{x5y0}#{x6y0}#{x7y0}#{x8y0}
</code></pre></div></div>

<p>This gives us a string like <code class="language-plaintext highlighter-rouge">879254316</code>. You can then use string matching to check that it contains every number from 1 to 9. You can’t do this with one string match, you have to do it nine separate times. A single check looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#{m:*1*,#{@x0y0}#{x1y0}#{x2y0}#{x3y0}#{x4y0}#{x5y0}#{x6y0}#{x7y0}#{x8y0}}
</code></pre></div></div>

<p>tmux will expand this to <code class="language-plaintext highlighter-rouge">1</code> if the first row contains a 1, and <code class="language-plaintext highlighter-rouge">0</code> if it does not. You then just have to do this for every other number:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#{m:*1*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*2*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*3*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*4*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*5*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*6*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*7*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*8*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
#{m:*9*,#{@x0y0}#{@x1y0}#{@x2y0}#{@x3y0}#{@x4y0}#{@x5y0}#{@x6y0}#{@x7y0}#{@x8y0}}
</code></pre></div></div>

<p>I’ve split this into multiple lines to give it some semblance of readability, but in the actual program this whole check is part of one <em>huge</em> line. It’s that snippet repeated eight more times for the rest of the rows, then nine more times for every column, and <em>then</em> nine more times for the 3x3 grids. It’s a <em>really</em> big string expansion.</p>

<p>Once expanded, this gives us a string of 243 ones and zeros. The resulting string will be entirely ones if the sudoku is solved. If there’s even a single zero, then we’ve got a number out of place. You just use another string match to check for any zeroes, and that tells you if the sudoku is solved.</p>

<p>The <code class="language-plaintext highlighter-rouge">if</code> (alias for <code class="language-plaintext highlighter-rouge">if-shell</code>) command in tmux will by default run the program in a sub shell and do the action depending on the exit status. However with the <code class="language-plaintext highlighter-rouge">-F</code> flag it’ll instead expand variables in the argument and treat it as truthy if it’s <code class="language-plaintext highlighter-rouge">1</code> or a non-empty string, and falsy if it’s <code class="language-plaintext highlighter-rouge">0</code> or empty. With this you can have branching control flow without the need to jump between windows like I’d done with the compiler.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">if</span> -<span class="n">F</span> <span class="s1">'#{window_zoomed_flag}'</span> {
  <span class="n">display</span> <span class="s2">"The window is zoomed"</span>
} {
  <span class="n">display</span> <span class="s2">"The window is not zoomed"</span>
}
</code></pre></div></div>

<p>That’s how you might use <code class="language-plaintext highlighter-rouge">if</code> in a tmux config file. It even supports braces (same as any other tmux command) and can be given an <code class="language-plaintext highlighter-rouge">else</code> block.</p>

<p>If you’ve <a href="https://github.com/sigh/Interactive-Sudoku-Solver">actually written a sudoku solver</a> you might think of all the optimisations you could make so that it can find the solution to a “hard” sudoku in a reasonable amount of time. This project is not about doing things reasonably, so I chose to just enumerate every possible solution, and check if it was solved every time.</p>

<p>My first approach was to use sessions as a call stack and use a recursive-style algorithm. The first session would check if the sudoku was solved, if not then it would increment the number in the first blank cell and create a new session to check the all combinations of the next blank cell. That second session would do the same and the recursion would continue for every blank cell until there were none left. If that didn’t solve it, then the sessions would all exit back to the start and you’d try again with the next number.</p>

<p>I poked around with this approach for a while, but struggled with how to create sessions on-the-fly that were setup with the necessary hooks to do computation, and how to indicate a return from one session to another. While tinkering with this, I realised that I didn’t actually need an arbitrarily large number of sessions, just one for every blank cell (i.e: at most 81 sessions), so I could just create them all upfront and swap between them instead of creating and exiting them constantly.</p>

<p>This made me also realise that I could just use windows instead of sessions, and move backwards and forwards as I recursed in and out. The session-local state would just be stored in window-specific options.</p>

<p>Before I had even started working out how to implement that, I had another realisation: I don’t have to move windows at all, I can use the <a href="/2024/03/16/further-adventures-in-tmux-code-evaluation/">recursive <code class="language-plaintext highlighter-rouge">send-keys</code> trick I’d come up with after writing the compiler</a>.</p>

<p>Go and read that <a href="/2024/03/16/further-adventures-in-tmux-code-evaluation/">whole post</a> if you want to understand the nuance, but basically what you do is create a tmux session with two windows. The first window is just a shell, but the second window is <em>also</em> tmux and is attached<sup id="fnref:nested" role="doc-noteref"><a href="#fn:nested" class="footnote" rel="footnote">1</a></sup> to the same session that it’s running in. Any keyboard input sent with <code class="language-plaintext highlighter-rouge">send-keys</code> into the second window will get sent back into tmux, and can trigger key bindings. If you want to jump from one block of code to another, you just <code class="language-plaintext highlighter-rouge">send-keys</code> the right letter(s) into the second window, and that block of code will be executed. As long as each block ends by sending a new key, the program will continue to run.</p>

<p>Doing this is a little precarious, since if you accidentally navigate to the second window, tmux will start to infinitely recurse and try to redraw the current window inside itself forever. This trick should only be attempted by trained professionals on a closed course.</p>

<p>All I needed to do now was to match the algorithm into separate steps, create keybindings for those steps, and use <code class="language-plaintext highlighter-rouge">send-keys</code> to trigger them automatically.</p>

<p>This was simple in hindsight, but it took some head-scratching to write out the algorithm in such a way that it could be mapped into keybindings. The algorithm looks something like this:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">start:</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">is_solved</span><span class="p">())</span> <span class="p">{</span>
    <span class="n">display</span><span class="p">(</span><span class="s">"Done!"</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">goto</span> <span class="n">compute_next</span><span class="p">;</span>
  <span class="p">}</span>

<span class="n">compute_next</span><span class="o">:</span>
  <span class="n">values</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">values</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">9</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">values</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">s</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">is_last_empty</span><span class="p">())</span> <span class="p">{</span>
      <span class="n">fail</span><span class="p">();</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">goto</span> <span class="n">compute_next</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">s</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">goto</span> <span class="n">start</span><span class="p">;</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>There are a few variables in play here:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">values</code> represents the grid, and we can subscript it by an index of a blank space. We know which spaces are blank because that’s precomputed for us. This actually takes multiple steps, but we can think of it as a single operation here.</li>
  <li><code class="language-plaintext highlighter-rouge">s</code> is the index of the blank space that we’re looking at.</li>
  <li>The <code class="language-plaintext highlighter-rouge">is_solved()</code> function represents our huge variable expansion that tells us whether the sudoku is in a solved state.</li>
  <li>The <code class="language-plaintext highlighter-rouge">is_last_empty()</code> function is an error case where the sudoku might not be solvable, so the algorithm continues off into a blank space that doesn’t exist.</li>
</ul>

<p>Before I drew this out as a transition diagram, I was thinking that I’d need to define far more keybindings—more like one per instruction—but in reality it’s just one keybinding for each point that control flow could jump back to. So I needed one keybinding for the code after <code class="language-plaintext highlighter-rouge">start:</code> and another for the code after <code class="language-plaintext highlighter-rouge">compute_next:</code>.</p>

<p>With that in mind, let’s look at the final tmux program, broken down into sections.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">source</span>-<span class="n">file</span> <span class="n">sudoku</span>-<span class="n">data</span>.<span class="n">conf</span>

<span class="n">new</span>-<span class="n">session</span>
<span class="n">new</span>-<span class="n">window</span> -<span class="n">d</span> <span class="s1">'TMUX= tmux attach -t "\$0"'</span>
</code></pre></div></div>

<p>Here we load the data which sets all the variables to store the grid. While this program includes template expansions for brevity and to avoid errors, you only need to expand the program once. After that it can solve any sudoku defined in the <code class="language-plaintext highlighter-rouge">sudoku-data.conf</code> file.</p>

<p>The config file actually creates the session for you (you can do that!) so you just <code class="language-plaintext highlighter-rouge">attach</code> to it immediately, instead of running <code class="language-plaintext highlighter-rouge">tmux new</code>. This allows me to create the second window and attach back to our session by running <code class="language-plaintext highlighter-rouge">tmux attach</code> in that second window.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bind</span> -<span class="n">n</span> <span class="n">n</span> {
  <span class="n">if</span> -<span class="n">F</span> <span class="s1">'&lt;%= grid_ok %&gt;'</span> {
    <span class="n">display</span> <span class="s1">'Solved!'</span>
    <span class="n">run</span> <span class="s2">"tmux set-buffer '&lt;%= show_state %&gt;'"</span>
    <span class="n">show</span>-<span class="n">buffer</span>
  } {
    <span class="n">send</span>-<span class="n">keys</span> -<span class="n">t</span> :<span class="m">1</span> <span class="n">G</span>
  }
}
</code></pre></div></div>

<p>This keybinding is for the code in <code class="language-plaintext highlighter-rouge">start:</code>. The conditional checks whether the sudoku is solved yet. The <code class="language-plaintext highlighter-rouge">&lt;%= grid_ok %&gt;</code> expansion is equivalent to the <code class="language-plaintext highlighter-rouge">is_solved()</code> function from above. This template function generates the huge variable expansion that I explained above.</p>

<p>The <code class="language-plaintext highlighter-rouge">set-buffer</code>/<code class="language-plaintext highlighter-rouge">show-buffer</code> steps are there to print the output in a nicely-formatted way once we’ve found the solution.</p>

<p>In the else block of the conditional is the interesting bit: it uses <code class="language-plaintext highlighter-rouge">send-keys</code> to type <code class="language-plaintext highlighter-rouge">G</code> into the second window (with an index of 1), which kicks off the rest of the program to get the board into the next possible state.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bind</span> -<span class="n">n</span> <span class="n">G</span> {
  <span class="n">rename</span>-<span class="n">window</span> <span class="s1">'@x&lt;%= x_sub "@blanks", "#S" %&gt;y&lt;%= y_sub "@blanks", "#S" %&gt;'</span>
  <span class="n">run</span> <span class="s2">"tmux set '#W' '#{e|+:1,#{E:##{#W#}}}'"</span>
  ...
}
</code></pre></div></div>

<p>This is where things go off the rails. What we’re actually doing here is looking into the <code class="language-plaintext highlighter-rouge">@blanks</code> “array” to find the next blank cell that we need to increment. We have to do some serious shenanigans to extract the two numbers out of the “array”.</p>

<p>I wrote two helper functions to do this, they are replaced with a nasty variable expansion, here’s the result of <code class="language-plaintext highlighter-rouge">&lt;%= x_sub "@blanks", "#S" %&gt;</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#{=1:#{=-#{e|-:#{n:@blanks},#{e|*:2,#S}}:@blanks}}
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">#{n:@blanks}</code> gives us the length of the “array”, <code class="language-plaintext highlighter-rouge">#{=1:</code> and <code class="language-plaintext highlighter-rouge">#{=-</code> truncates the string to a set number of  from the start or end, and <code class="language-plaintext highlighter-rouge">#{e|-:</code> and <code class="language-plaintext highlighter-rouge">#{e|*:</code> do arithmetic. Put this all together <em>very</em> carefully, and you get a single number from the <code class="language-plaintext highlighter-rouge">@blanks</code> array. This took a lot of trial and error to build up, and being able to break it into multiple lines in the Ruby script was really helpful. It’s really annoying when you miss a single closing brace and your triply-nested expansion does something completely unexpected.</p>

<p><code class="language-plaintext highlighter-rouge">y_sub</code> is basically the same, except it offsets by one to get the second character from the pair. I use the session name as the index of which blank cell I’m looking at as it’s a convenient place to store a single number—I did the same thing with the Brainfuck compiler.</p>

<p>These few lines could be done all in one, but things were being a bit weird so I decided to split it up and use the window name as a scratch storage space. I first store the variable name of the cell position (like <code class="language-plaintext highlighter-rouge">@x6y3</code>), then increment that variable by one. <code class="language-plaintext highlighter-rouge">#{E:##{#W#}}</code> does a double-expansion, so it expands <code class="language-plaintext highlighter-rouge">#W</code> (the window name) into the variable name, then expands <em>that</em> into the value of the variable.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">if</span> -<span class="n">F</span> <span class="s1">'#{e|&gt;:#{E:##{#W#}},9}'</span> {
    <span class="n">run</span> <span class="s2">"tmux set '#W' '1'"</span>
    <span class="n">rename</span>-<span class="n">session</span> <span class="s1">'#{e|+:1,#S}'</span>
    <span class="n">if</span> -<span class="n">F</span> <span class="s1">'&lt;%= more_array? "@blanks", "#S" %&gt;'</span> {
      <span class="n">send</span>-<span class="n">keys</span> -<span class="n">t</span> :<span class="m">1</span> <span class="n">G</span>
    } {
      <span class="n">display</span> <span class="s1">'FAILED! oh no!'</span>
    }
  } {
    ...
  }
}
</code></pre></div></div>

<p>The conditional here checks if the value we just incremented is greater than nine. If it is, we need to overflow to the next blank cell. We reset the current blank cell back to <code class="language-plaintext highlighter-rouge">1</code>, increment the session name—so that when we jump back we’ll be incrementing the next blank cell. Before we jump we check that there is another number in the <code class="language-plaintext highlighter-rouge">@blanks</code> “array”, the <code class="language-plaintext highlighter-rouge">more_array?</code> function generates this expansion:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#{e|&lt;:#{e|*:2,#S},#{n:@blanks}}
</code></pre></div></div>

<p>Which checks that there are enough pairs of characters in <code class="language-plaintext highlighter-rouge">@blanks</code> for the newly-incremented session name <code class="language-plaintext highlighter-rouge">#S</code>.</p>

<p>If there are, then we <code class="language-plaintext highlighter-rouge">send-keys</code> a <code class="language-plaintext highlighter-rouge">G</code> and processing jumps back to the top of <code class="language-plaintext highlighter-rouge">compute_next:</code>, incrementing the next blank cell and overflowing if necessary. If we don’t have another cell to increment, then we must have an unsolvable sudoku—since we’ve tried every combination at this point—so we just show an error message and give up.</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">if</span> -<span class="n">F</span> <span class="s1">'#{e|&gt;:#{E:##{#W#}},9}'</span> {
    ...
  } {
    <span class="n">rename</span>-<span class="n">session</span> <span class="s1">'0'</span>
    <span class="n">send</span>-<span class="n">keys</span> -<span class="n">t</span> :<span class="m">1</span> <span class="n">n</span>
  }
</code></pre></div></div>

<p>If we don’t need to overflow, we’ve set the sudoku grid into the next state so we can jump back to <code class="language-plaintext highlighter-rouge">start:</code> and check if we’ve got the solution. We set the session name back to <code class="language-plaintext highlighter-rouge">0</code> since we need to increment from the first blank cell again if this isn’t the solution.</p>

<p>I knew in the back of my mind when I wrote the Python-tmux compiler that what I was doing was horribly inefficient, but to have the sudoku solver boil down to just two key bindings really shows just how much of a brute-force solution the compiler was. You could probably make a much more efficient and useful tmux compiler by only jumping to a new window/key binding/etc when the code needs to jump as part of a loop or function call, instead of for every instruction. But that’s a project for another time.</p>

<p>Now, how fast is it?</p>

<p>I made a few decorative modifications to the program, allowing it to be quit more easily, visualise the progress, write the solution to a file, and exit on completion (have a look at <a href="https://github.com/willhbr/tmux-sudoku">the repo</a> for the final program). This made it easier to run it with <code class="language-plaintext highlighter-rouge">time</code> without me having to sit there waiting for it to complete.</p>

<p>I started with a solved sudoku and progressively deleted more numbers from it. With 4 numbers missing, it solved it in 41 seconds. With 5 missing, it took 6:40.<sup id="fnref:on-nuc" role="doc-noteref"><a href="#fn:on-nuc" class="footnote" rel="footnote">2</a></sup></p>

<p>While trying to solve a sudoku with 6 numbers missing, I worked out from the status line display of the current grid state that the third missing number was incrementing once every 700ms. That means the grid is being updated 81 times in 700ms, so 8.6ms per potential solution.</p>

<p>After extensive research with <a href="https://www.playgoodsudoku.com">Good Sudoku</a> I found that a typical “easy” had about 35 pre-placed numbers, so 46 missing numbers. That’s 9<sup>46</sup> solutions that my incredibly dumb solver would have to try.</p>

<p>9<sup>46</sup> is 78x10<sup>43</sup>. Multiply that by 8.6ms and you get 6.8x10<sup>41</sup> seconds. This is a very, very long time. If you’ve done a sudoku before then you can probably solve it faster.<sup id="fnref:samply" role="doc-noteref"><a href="#fn:samply" class="footnote" rel="footnote">3</a></sup></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:nested" role="doc-endnote">
      <p>sessions should be nested with care, unset <code class="language-plaintext highlighter-rouge">$TMUX</code> to force. <a href="#fnref:nested" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:on-nuc" role="doc-endnote">
      <p>This is running on an Intel NUC with an i5-10210U processor. <a href="#fnref:on-nuc" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:samply" role="doc-endnote">
      <p>I did take a quick trace using <a href="https://github.com/mstange/samply">Samply</a> and tmux is obviously spending most of its time expanding format strings and getting options to put in format strings. <a href="#fnref:samply" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>


<a href="https://willhbr.net/2024/12/27/solving-sudoku-with-tmux/">Permalink</a>
&bull; December 27, 2024
&bull; Send feedback via
  <a href="https://ruby.social/@willhbr" target=_blank>mastodon</a> or
  
  <a href="mailto:feedback@willhbr.net?subject=Feedback:%20Solving%20Sudoku%20with%20tmux" target=_blank>email</a>.


guid: https://willhbr.net/2025/01/06/lazy-load-command-completions-for-a-faster-shell-startup/
title: Lazy Load Command Completions for a Faster Shell Startup
url: https://willhbr.net/2025/01/06/lazy-load-command-completions-for-a-faster-shell-startup/
feedurl: https://willhbr.net/feed.xml
content:
<p>It’s a new year and you know what that means: time to remove unnecessary work from your shell startup.<sup id="fnref:it-matters" role="doc-noteref"><a href="#fn:it-matters" class="footnote" rel="footnote">1</a></sup></p>

<p>Throughout the year there’s always a little more bloat that creeps in. Like a frog in a pot you don’t notice it until you’re sitting there waiting hundreds of milliseconds for your shell to load. Well you deserve better, so take the time to remove unnecessary cruft and trim down that load time.</p>

<p>If you haven’t done this before, <a href="https://blog.mattclemente.com/2020/06/26/oh-my-zsh-slow-to-load/">this post by Matthew Clemente</a> goes into much more detail on debugging startup time in ZSH.</p>

<p>Since I use <a href="/2017/08/21/pug-an-abomination-of-shell-scripting/">my own plugin system</a> a lot of the oh-my-zsh debugging isn’t relevant, and I just end up commenting out blocks of code and using this hack to get the load time:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>repeat 10 <span class="nb">time </span>zsh <span class="nt">-i</span> <span class="nt">-c</span> <span class="nb">exit</span>
<span class="go">zsh -i -c exit  0.09s user 0.02s system 102% cpu 0.106 total
zsh -i -c exit  0.07s user 0.03s system 102% cpu 0.102 total
zsh -i -c exit  0.08s user 0.02s system 101% cpu 0.103 total
zsh -i -c exit  0.08s user 0.03s system 101% cpu 0.103 total
zsh -i -c exit  0.08s user 0.03s system 102% cpu 0.106 total
zsh -i -c exit  0.08s user 0.03s system 102% cpu 0.103 total
zsh -i -c exit  0.07s user 0.03s system 101% cpu 0.101 total
zsh -i -c exit  0.08s user 0.03s system 101% cpu 0.107 total
zsh -i -c exit  0.07s user 0.04s system 101% cpu 0.105 total
zsh -i -c exit  0.08s user 0.03s system 101% cpu 0.109 total
</span></code></pre></div></div>

<p>A little over 100ms isn’t too bad, but I’ve found something that’s contributing about 50ms to that which I can cut out completely.</p>

<p>The culprit is the <a href="https://jj-vcs.github.io/jj/latest/install-and-setup/#zsh">JJ ZSH autocomplete</a>:</p>

<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> &lt;<span class="o">(</span>jj util completion zsh<span class="o">)</span>
</code></pre></div></div>

<p>If I remove this, the startup time drops to around 56ms. That’s almost half the startup time just loading JJ autocomplete. Loading the other plugins I use (<a href="https://github.com/zsh-users/zsh-autosuggestions">zsh-autosuggestions</a> and <a href="https://github.com/zsh-users/zsh-syntax-highlighting">zsh-syntax-highlighting</a>) only takes 10ms combined, so 50ms for one completion definition is a long time.</p>

<p>Disabling the autocomplete <em>is</em> an option, but autocomplete is nice and I’d probably live through the additional 50ms if there wasn’t another option. Thankfully I can get the best of both worlds by loading the JJ autocomplete only when I run <code class="language-plaintext highlighter-rouge">jj</code> for the first time. I’ve done this by wrapping <code class="language-plaintext highlighter-rouge">jj</code> in a shell function:</p>

<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jj<span class="o">()</span> <span class="o">{</span>
  <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$JJ_LOADED</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">source</span> &lt;<span class="o">(</span><span class="nb">command </span>jj util completion zsh<span class="o">)</span>
    <span class="nv">JJ_LOADED</span><span class="o">=</span><span class="nb">true
  </span><span class="k">fi
  </span><span class="nb">command </span>jj <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
<span class="o">}</span>
</code></pre></div></div>

<p>On the first run, since <code class="language-plaintext highlighter-rouge">$JJ_LOADED</code> is unset, it’ll source the autocompletion code before running <code class="language-plaintext highlighter-rouge">jj</code>. On subsequent runs, it’ll just delegate to <code class="language-plaintext highlighter-rouge">jj</code> immediately. This does mean that the first interaction with JJ will be ~50ms slower, but because that additional wait is in the context of running a command, it’s barely noticeable. The result is a much faster startup:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nv">$repeat</span> 10 <span class="nb">time </span>zsh <span class="nt">-i</span> <span class="nt">-c</span> <span class="nb">exit</span>
<span class="go">zsh -i -c exit  0.05s user 0.02s system 99% cpu 0.065 total
zsh -i -c exit  0.04s user 0.01s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.02s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.02s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.01s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.02s system 99% cpu 0.057 total
zsh -i -c exit  0.04s user 0.02s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.02s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.01s system 99% cpu 0.056 total
zsh -i -c exit  0.04s user 0.01s system 99% cpu 0.058 total
</span></code></pre></div></div>

<p>You could probably use this same trick<sup id="fnref:defer" role="doc-noteref"><a href="#fn:defer" class="footnote" rel="footnote">2</a></sup> to only load your favourite language version manager when you’re actually going to use it.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:it-matters" role="doc-endnote">
      <p>Unless your one of those people that opens one shell at the start of the day and never opens a new tab, pane, or window. Then it doesn’t matter how fast your shell is, it could take a full minute to start and you’d probably barely notice. I, on the other hand, open new shells like there’s no tomorrow. <a href="#fnref:it-matters" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:defer" role="doc-endnote">
      <p>Something like <a href="https://github.com/romkatv/zsh-defer">zsh-defer</a> would also work. <a href="#fnref:defer" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>


<a href="https://willhbr.net/2025/01/06/lazy-load-command-completions-for-a-faster-shell-startup/">Permalink</a>
&bull; January 6, 2025
&bull; Send feedback via
  <a href="https://ruby.social/@willhbr" target=_blank>mastodon</a> or
  
  <a href="mailto:feedback@willhbr.net?subject=Feedback:%20Lazy%20Load%20Command%20Completions%20for%20a%20Faster%20Shell%20Startup" target=_blank>email</a>.


guid: https://willhbr.net/2025/01/10/how-did-i-miss-run-c/
title: How Did I Miss run -C?
url: https://willhbr.net/2025/01/10/how-did-i-miss-run-c/
feedurl: https://willhbr.net/feed.xml
content:
<p>It’s important to remember that there’s always something new to learn. While I wrote my <a href="/2024/03/15/making-a-compiler-to-prove-tmux-is-turing-complete/">tmux compiler</a> and <a href="/2024/12/27/solving-sudoku-with-tmux/">tmux sudoku solver</a> as well as during a bunch of other projects, I have spent a lot of time reading the tmux man page. Yet I missed a critical detail in the <code class="language-plaintext highlighter-rouge">run-shell</code> section.</p>

<p>A significant caveat to both the compiler and tmux solver is that I needed to start a sub-shell to call back into tmux allow me to expand variables anywhere in the command:</p>

<blockquote>
  <p>Only certain arguments to tmux commands have variable expansion on them, but the whole string passed to run is expanded, which means we can use variables anywhere in any tmux command. For example:</p>

  <p>This will add a buffer containing the literal string ‘#{session_name}’:</p>

  <div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span>-<span class="n">buffer</span> <span class="s1">'#{session_name}'</span>
</code></pre></div>  </div>

  <p>But this will add a buffer containing whatever the current session name is:</p>
  <div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">run</span> <span class="s2">"set-buffer '#{session_name}'"</span>
</code></pre></div>  </div>
</blockquote>

<p>And sitting right under my nose this whole time was <code class="language-plaintext highlighter-rouge">run -C</code>.</p>

<p><code class="language-plaintext highlighter-rouge">run -C "..."</code> is equivalent to <code class="language-plaintext highlighter-rouge">run 'tmux "..."'</code>. Instead of spawning a shell and running the <code class="language-plaintext highlighter-rouge">tmux</code> binary, the argument is expanded and directly interpreted by tmux as a tmux command. It’s exactly what I wanted to do.</p>

<p>Not only does this mean I can remove the caveat of “I’m shelling out but it’s only to call tmux again”, since it doesn’t have to waste time spawning a new process, it’s also much faster.</p>

<p>The time taken to solve a sudoku with four numbers missing went from 41 seconds down to about 11 seconds. The interval between number increments seems to be about 3.7ms, down from 8.6ms.</p>

<p>However, this comes at a cost. It seems that tmux can only process keybindings so fast, and this is now fast enough that a key press gets sent to the second window and instead of getting processed as a key binding, it gets passed through to the shell. The program relies on these being processed to continue running, so the sudoku solver stops here and the user has to press <code class="language-plaintext highlighter-rouge">n</code> to have the program continue.</p>

<p>So there you go, you probably learnt two things today:</p>

<ol>
  <li>tmux has a command for running tmux commands</li>
  <li>tmux keybindings become unreliable if you’re pressing around 270 keys per second</li>
</ol>


<a href="https://willhbr.net/2025/01/10/how-did-i-miss-run-c/">Permalink</a>
&bull; January 10, 2025
&bull; Send feedback via
  <a href="https://ruby.social/@willhbr" target=_blank>mastodon</a> or
  
  <a href="mailto:feedback@willhbr.net?subject=Feedback:%20How%20Did%20I%20Miss%20run%20-C?" target=_blank>email</a>.


guid: https://willhbr.net/2025/01/18/configure-rootless-podman-remote-on-alpine-linux/
title: Configure Rootless Podman Remote on Alpine Linux
url: https://willhbr.net/2025/01/18/configure-rootless-podman-remote-on-alpine-linux/
feedurl: https://willhbr.net/feed.xml
content:
<p>I thought this was going to be easy. I’ve <a href="/2023/05/05/setting-up-podman-remote/">been using</a> <code class="language-plaintext highlighter-rouge">podman-remote</code> to manage containers across my two home servers for ages, but only ever on Ubuntu. It turns out there are a few tricky things that aren’t easy to debug or documented anywhere, so here’s how to get rootless <code class="language-plaintext highlighter-rouge">podman-remote</code> working on an Alpine Linux host.</p>

<p>Podman is available in the community APK repository, so either enable that during setup, or uncomment the line in <code class="language-plaintext highlighter-rouge">/etc/apk/repositories</code> then run:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>apk add podman
</code></pre></div></div>

<p>You’ll then need to assign some more user IDs to yourself. I don’t fully understand the mechanics of the user ID business in Podman, but you just need to edit these two files (changing the username, obviously):</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">echo </span>will:100000:65536 <span class="o">&gt;</span> /etc/subuid
<span class="gp">$</span><span class="w"> </span><span class="nb">echo </span>will:100000:65536 <span class="o">&gt;</span> /etc/subgid
</code></pre></div></div>

<p>Then enable and start the <code class="language-plaintext highlighter-rouge">cgroups</code> service:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rc-update add cgroups
$ service cgroups start
</code></pre></div></div>

<p>Now you should be able to run a container:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>podman run <span class="nt">-it</span> alpine:latest
<span class="gp">/ #</span><span class="w">
</span></code></pre></div></div>

<p>That’s great, but we want <code class="language-plaintext highlighter-rouge">podman-remote</code>. On Debian systems you just need to <a href="/2023/05/05/setting-up-podman-remote/">start a user-level <code class="language-plaintext highlighter-rouge">systemd</code> unit</a>, but we don’t have <code class="language-plaintext highlighter-rouge">systemd</code> on Alpine. Thankfully podman has an OpenRC config, which we just need to tweak to run rootless.</p>

<p>All the <code class="language-plaintext highlighter-rouge">systemd</code> unit is actually doing is running the <a href="https://docs.podman.io/en/latest/markdown/podman-system-service.1.html"><code class="language-plaintext highlighter-rouge">podman system service</code></a> command. That will start a server that listens on a UNIX or TCP socket and exposes the Podman API. You can run it yourself without any supervision:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>podman system service <span class="nt">-t</span> 0 unix:///home/will/podman.sock
<span class="c">...
</span></code></pre></div></div>

<p>Then in a separate console you can connect to that socket using <code class="language-plaintext highlighter-rouge">podman --url unix:///home/will/podman.sock</code>.</p>

<p>We do want our podman to be supervised, however. We just need to edit <code class="language-plaintext highlighter-rouge">/etc/conf.d/podman</code> to set the URI and user that Podman will run as:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/conf.d/podman
</span><span class="n">podman_uri</span>=<span class="s2">"unix:///home/will/podman.sock"</span>
<span class="n">podman_user</span>=<span class="s2">"will"</span>
</code></pre></div></div>

<p>This assumes that we’ll only have one podman-remote user. I don’t know what you need to do to get multiple users working, but since I’m just wanting to get one rootless user to manage my containers, this should be fine.</p>

<p>We can then enable and start the <code class="language-plaintext highlighter-rouge">podman</code> service:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rc-update add podman
<span class="gp">$</span><span class="w"> </span>service podman start
</code></pre></div></div>

<p>This will now start the podman daemon on boot using the socket and user we configured. Before we get remote access working, check that you can connect to the socket:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>podman <span class="nt">--url</span> unix:///home/will/podman.sock ps
<span class="go">CONTAINER ID  IMAGE  COMMAND  CREATED  STATUS  PORTS  NAMES
</span></code></pre></div></div>

<p>And then on your remote machine, check that you can connect over SSH:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>podman <span class="nt">--url</span> ssh://will@alpine-server:22/home/will/podman.sock ps
</code></pre></div></div>

<p>Podman will likely complain about not being able to open the connection, which after much trial and error I worked out was because the default Alpine OpenSSH config doesn’t enable TCP forwarding, whereas the Ubuntu config does. I enabled it in <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code>:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/ssh/sshd_config
</span><span class="n">AllowTcpForwarding</span> <span class="n">yes</span>
</code></pre></div></div>

<p>After restarting <code class="language-plaintext highlighter-rouge">sshd</code> (<code class="language-plaintext highlighter-rouge">service sshd restart</code>) that should be enough to get things working.</p>

<p>At this point you should add the config to <code class="language-plaintext highlighter-rouge">~/.config/containers/containers.conf</code> so you don’t have to type out <code class="language-plaintext highlighter-rouge">--url</code> every time:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[<span class="n">engine</span>.<span class="n">service_destinations</span>.<span class="n">alpine</span>-<span class="n">server</span>]
<span class="n">uri</span> = <span class="s2">"ssh://will@alpine-server:22/home/will/podman.sock"</span>
<span class="n">identity</span> = <span class="s2">"/home/will/.ssh/id_ed25519"</span>
</code></pre></div></div>

<p>Then you can run <code class="language-plaintext highlighter-rouge">podman -c alpine-server</code> to do anything on the remote machine.</p>

<p>Originally I ran into a problem at this point where podman would constantly complain of mismatching host keys, but connecting with <code class="language-plaintext highlighter-rouge">ssh</code> would work just fine. If I deleted the entry in <code class="language-plaintext highlighter-rouge">.ssh/known_hosts</code> and connected with podman first it would work, but then connecting with <code class="language-plaintext highlighter-rouge">ssh</code> would complain of a mismatching key. Eventually I worked out that I could re-order the keys in <code class="language-plaintext highlighter-rouge">.ssh/known_hosts</code> and have both work, but that seemed like a horrible hack.</p>

<p>I thought this was a difference in the Go SSH client and the OpenSSH CLI, but it ended up being the hardened SSH config that <a href="/2023/05/09/hardening-with-ssh-audit/">I setup a while ago</a>. The <code class="language-plaintext highlighter-rouge">ssh</code> CLI wouldn’t add the <code class="language-plaintext highlighter-rouge">ecdsa-sha2-nistp256</code> key, whereas Podman would <em>only</em> add that key. The end result is they both think something nefarious is going on because the key in <code class="language-plaintext highlighter-rouge">known_hosts</code> doesn’t match. Adding the hardened sshd config from <a href="https://ssh-audit.com">ssh-audit</a> would make it work because Podman would be forced to use <code class="language-plaintext highlighter-rouge">ssh-ed25519</code> instead, which is what <code class="language-plaintext highlighter-rouge">ssh</code> expects. Removing the hardened ssh config from the client made it work without any issues as <code class="language-plaintext highlighter-rouge">ssh</code> would add the <code class="language-plaintext highlighter-rouge">ecdsa-sha2-nistp256</code> key to <code class="language-plaintext highlighter-rouge">known_hosts</code>, so Podman would see what it expected.</p>

<p>The aim of this exercise was to be able to setup a minimal host OS install with a single non-root user that will run containers. The host OS should have as little as possible installed, and the setup should be as scriptable as possible. Automating the setup in this post is a story for another time.</p>


<a href="https://willhbr.net/2025/01/18/configure-rootless-podman-remote-on-alpine-linux/">Permalink</a>
&bull; January 18, 2025
&bull; Send feedback via
  <a href="https://ruby.social/@willhbr" target=_blank>mastodon</a> or
  
  <a href="mailto:feedback@willhbr.net?subject=Feedback:%20Configure%20Rootless%20Podman%20Remote%20on%20Alpine%20Linux" target=_blank>email</a>.


guid: 807fcda09e3f32cccc7b6171794391877b5473cf/generators
title: Generators
url: https://voussoir.net/writing/generators
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="programming.python" href="/writing/tags/python">[python]</a> <a class="tag_link" data-qualname="tutorial" href="/writing/tags/tutorial">[tutorial]</a></p>
<h1>Generators</h1><ol id="table_of_contents">Table of contents<li><a href="#what_are_they">What are they</a></li><li><a href="#writing_one">Writing one</a></li><li><a href="#using_one">Using one</a></li><li><a href="#stopiteration">StopIteration</a></li><li><a href="#minor_notes">Minor notes</a></li><li><a href="#more_examples">More examples</a><ol><li><a href="#yielding_individual_items_from_batches">Yielding individual items from batches</a></li></ol></li><li><a href="#further_reading">Further reading</a></li></ol>
<h2 id="what_are_they">What are they<a class="header_anchor_link" href="#what_are_they"> (§)</a></h2>
<p>Generators are a type of iterable that create their contents on the fly. Unlike a list, whose entire contents are available before beginning any loops or manipulations, generators don't know how many items they will produce or when they will stop. They can even go on forever!</p>
<h2 id="writing_one">Writing one<a class="header_anchor_link" href="#writing_one"> (§)</a></h2>
<p>Writing a generator looks like writing a function, but instead of <code>return</code>, you use <code>yield</code>. The object which is yielded is what you'll get when you do a loop over the generator. This one lets you count to a billion:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">billion</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
    <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="mi">1000000000</span><span class="p">:</span>
        <span class="k">yield</span> <span class="n">x</span>
        <span class="n">x</span> <span class="o">+</span><span class="o">=</span> <span class="mi">1</span>
</pre></div><p>I purposely used a <code>while</code> loop instead of <code>for x in range()</code> to show the extra work.</p>
<p>Note that, unlike a <code>return</code> statement, you can include more code after a <code>yield</code> statement. Also notice that generators keep track of their internal state — the <code>billion</code> generator has an <code>x</code> that it increments every time you loop over it. You can imagine the code pausing after the <code>yield</code> line, and resuming when you come back for the next cycle. Try this with some extra print statements to help visualize.</p>
<p>Generators can also take arguments. Here's a generator that counts to a custom amount:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">count_to</span><span class="p">(</span><span class="narg">y</span><span class="p">)</span><span class="p">:</span>
    <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">y</span><span class="p">:</span>
        <span class="k">yield</span> <span class="n">x</span>
        <span class="n">x</span> <span class="o">+</span><span class="o">=</span> <span class="mi">1</span>
</pre></div><h2 id="using_one">Using one<a class="header_anchor_link" href="#using_one"> (§)</a></h2>
<p>Although generators look like functions when you're writing them, they feel more like classes when using them. Remember that generators don't calculate their contents until they are actually used in a loop, so simply doing:</p>
<div class="highlight python"><pre><span class="n">numbers</span> <span class="o">=</span> <span class="n">count_to</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
</pre></div><p>does <strong>not</strong> create a list of 100 numbers. It creates a new instance of the generator that is ready to be iterated over, like this:</p>
<div class="highlight python"><pre><span class="n">numbers</span> <span class="o">=</span> <span class="n">count_to</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
<span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
</pre></div><p>or this:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="n">count_to</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
</pre></div><p>This should remind you of:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
</pre></div><p>because the <code>range</code> class behaves a lot like a generator (<a href="http://stackoverflow.com/a/13092317">but not exactly</a>).</p>
<p>Generators are excellent for cases where using a list is infeasible or unnecessary. In order to loop over a list, you have to generate the entire thing first. If you're only going to use each item once, storing the entire list can be a big memory waste when a generator could take its place. With a generator, the items are created, used, and thrown away, so memory can be recycled.</p>
<p>Since generators can go on forever, they're great for abstracting out ugly <code>while</code> loops, so you can get down to business faster.</p>
<p>To get a single item from a generator without looping, use <code>next(generator)</code>.</p>
<h2 id="stopiteration">StopIteration<a class="header_anchor_link" href="#stopiteration"> (§)</a></h2>
<p>Generators pause and resume a lot, but they still flow like normal functions. As long as there is no endless <code>while</code> loop inside, they'll come to an end at some point. When a generator is all finished, it will raise a <code>StopIteration</code> exception every time you try to do <code>next()</code> on it. Luckily, <code>for</code> loops will detect this automatically and stop themselves.</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">g</span> <span class="o">=</span> <span class="n">count_to</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span>&gt;&gt;&gt;</span> <span class="n">l</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>
<span>&gt;&gt;&gt;</span> <span class="nb">next</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>
<span>Traceback</span> <span>(</span><span>most</span> <span>recent</span> <span>call</span> <span>last</span><span>)</span><span>:</span>
<span></span><span>File</span> <span>"</span><span>&lt;stdin&gt;</span><span>"</span><span>,</span> <span>line</span> <span>1</span><span>,</span> <span>in</span> <span>&lt;</span><span>module</span><span>&gt;</span>
<span>StopIteration</span>
</pre></div><p>Earlier, I said that generators use <code>yield</code> instead of <code>return</code>, but in fact you can include a return statement too. If it is encountered, it will raise a <code>StopIteration</code>, and the generator will not resume even if there is more code.</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">generator</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="k">yield</span> <span class="mi">1</span>
<span>...</span>     <span class="k">return</span> <span class="mi">2</span>
<span>...</span>     <span class="k">yield</span> <span class="mi">3</span>
<span>...</span>
<span>&gt;&gt;&gt;</span>
<span>&gt;&gt;&gt;</span> <span class="n">g</span> <span class="o">=</span> <span class="n">generator</span><span class="p">(</span><span class="p">)</span>
<span>&gt;&gt;&gt;</span> <span class="nb">next</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>
<span>1</span>
<span>&gt;&gt;&gt;</span> <span class="nb">next</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>
<span>Traceback</span> <span>(</span><span>most</span> <span>recent</span> <span>call</span> <span>last</span><span>)</span><span>:</span>
<span></span><span>File</span> <span>"</span><span>&lt;stdin&gt;</span><span>"</span><span>,</span> <span>line</span> <span>1</span><span>,</span> <span>in</span> <span>&lt;</span><span>module</span><span>&gt;</span>
<span>StopIteration</span><span>:</span> <span>2</span>
<span>&gt;&gt;&gt;</span> <span class="nb">next</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>
<span>Traceback</span> <span>(</span><span>most</span> <span>recent</span> <span>call</span> <span>last</span><span>)</span><span>:</span>
<span></span><span>File</span> <span>"</span><span>&lt;stdin&gt;</span><span>"</span><span>,</span> <span>line</span> <span>1</span><span>,</span> <span>in</span> <span>&lt;</span><span>module</span><span>&gt;</span>
<span>StopIteration</span>
<span>&gt;&gt;&gt;</span>
</pre></div><p>Notice that the 2 became part of the StopIteration that was raised at that line.</p>
<h2 id="minor_notes">Minor notes<a class="header_anchor_link" href="#minor_notes"> (§)</a></h2>
<ul>
<li>You cannot access the items of a generator by index, because only one item exists at a time. Once you do a loop over a generator, those items are gone forever unless you kept them somewhere else.</li>
</ul>
<h2 id="more_examples">More examples<a class="header_anchor_link" href="#more_examples"> (§)</a></h2>
<h3 id="yielding_individual_items_from_batches">Yielding individual items from batches<a class="header_anchor_link" href="#yielding_individual_items_from_batches"> (§)</a></h3>
<p>Suppose you're getting data from an imaginary website which sends you items in groups of 100. You want to let the user loop over the items without having to worry about the groups.</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">item_generator</span><span class="p">(</span><span class="narg">url</span><span class="p">)</span><span class="p">:</span>
    <span class="n">page</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
        <span class="c1"># get_items is a pretend method that collects the 100 items from that page</span>
        <span class="n">batch</span> <span class="o">=</span> <span class="n">get_items</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="narg">page</span><span class="o">=</span><span class="n">page</span><span class="p">)</span>

        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">batch</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="c1"># for this imaginary website, the batch will be empty when that page</span>
            <span class="c1"># doesn't have any items on it.</span>
            <span class="k">break</span>

        <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">batch</span><span class="p">:</span>
            <span class="c1"># by yielding individual items, the user can just do a for loop</span>
            <span class="c1"># over this generator and get them all one by one.</span>
            <span class="k">yield</span> <span class="n">item</span>

        <span class="n">page</span> <span class="o">+</span><span class="o">=</span> <span class="mi">1</span>

    <span class="c1"># When the while loop breaks, we reach the end of the function body,</span>
    <span class="c1"># concluding the generator.</span>
</pre></div><p>Now the user can just do this:</p>
<div class="highlight python"><pre><span class="n">comments</span> <span class="o">=</span> <span class="n">item_generator</span><span class="p">(</span><span class="s1">'</span><span class="s1">http://example.com/user/voussoir/comments</span><span class="s1">'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">comment</span> <span class="ow">in</span> <span class="n">comments</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">comment</span><span class="n">.</span><span class="n">body</span><span class="p">)</span>
</pre></div><h2 id="further_reading">Further reading<a class="header_anchor_link" href="#further_reading"> (§)</a></h2>
<p><a href="http://stackoverflow.com/questions/9708902/in-practice-what-are-the-main-uses-for-the-new-yield-from-syntax-in-python-3">Stack Overflow - What are the main uses for <code>yield from</code>?</a> — If you like recursive functions, how about recursive generators?</p>
<p><a href="http://stackoverflow.com/questions/19302530/python-generator-send-function-purpose">Stack Overflow - Python generator <code>send</code> function purpose?</a> — This quickly dives out of "quick tips" territory.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/generators/generators.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/807fcda09e3f32cccc7b6171794391877b5473cf"><time datetime="2020-03-14T21:41:53-07:00">2020-03-14</time> Add Generators and Continue articles to test the waters.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 807fcda09e3f32cccc7b6171794391877b5473cf/continue
title: Continue
url: https://voussoir.net/writing/continue
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="programming.python" href="/writing/tags/python">[python]</a> <a class="tag_link" data-qualname="tutorial" href="/writing/tags/tutorial">[tutorial]</a></p>
<h1>Continue</h1><ol id="table_of_contents">Table of contents<li><a href="#what_is_it">What is it</a></li><li><a href="#using_it">Using it</a><ol><li><a href="#continue_is_great_for_cleaning_code_with_lots_of_conditions">Continue is great for cleaning code with lots of conditions</a></li></ol></li></ol>
<h2 id="what_is_it">What is it<a class="header_anchor_link" href="#what_is_it"> (§)</a></h2>
<p><code>continue</code> is a keyword used to control <code>for</code> and <code>while</code> loops. It skips the rest of the current iteration, and starts the next one. This is different from <code>break</code> which ends the entire loop.</p>
<h2 id="using_it">Using it<a class="header_anchor_link" href="#using_it"> (§)</a></h2>
<p>This loop skips the number 3:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="k">if</span> <span class="n">x</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
<span>...</span>             <span class="k">continue</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span>...</span>
<span>0</span>
<span>1</span>
<span>2</span>
<span>4</span>
<span>5</span>
</pre></div><p>Here, a bit more verbose:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s1"> is above the continue</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>     <span class="k">if</span> <span class="n">x</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
<span>...</span>             <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s1"> is getting skipped</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>             <span class="k">continue</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s1"> is below the continue</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>0</span> <span>is</span> <span>above</span> <span>the</span> <span>continue</span>
<span>0</span> <span>is</span> <span>below</span> <span>the</span> <span>continue</span>
<span>1</span> <span>is</span> <span>above</span> <span>the</span> <span>continue</span>
<span>1</span> <span>is</span> <span>below</span> <span>the</span> <span>continue</span>
<span>2</span> <span>is</span> <span>above</span> <span>the</span> <span>continue</span>
<span>2</span> <span>is</span> <span>below</span> <span>the</span> <span>continue</span>
<span>3</span> <span>is</span> <span>above</span> <span>the</span> <span>continue</span>
<span>3</span> <span>is</span> <span>getting</span> <span>skipped</span>
<span>4</span> <span>is</span> <span>above</span> <span>the</span> <span>continue</span>
<span>4</span> <span>is</span> <span>below</span> <span>the</span> <span>continue</span>
<span>5</span> <span>is</span> <span>above</span> <span>the</span> <span>continue</span>
<span>5</span> <span>is</span> <span>below</span> <span>the</span> <span>continue</span>
</pre></div><p>This loop skips any directories that can't be <code>listdir</code>'ed:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">directory</span> <span class="ow">in</span> <span class="n">directories</span><span class="p">:</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">filenames</span> <span class="o">=</span> <span class="n">os</span><span class="n">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
    <span class="k">except</span> <span class="ne">PermissionError</span><span class="p">:</span>
        <span class="k">continue</span>

    <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">:</span>
        <span>...</span>
</pre></div><h3 id="continue_is_great_for_cleaning_code_with_lots_of_conditions">Continue is great for cleaning code with lots of conditions<a class="header_anchor_link" href="#continue_is_great_for_cleaning_code_with_lots_of_conditions"> (§)</a></h3>
<p>If you're trying to perform an action on items that meet multiple criteria, you will either have to nest the conditions:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">submission</span> <span class="ow">in</span> <span class="n">submissions</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">submission</span><span class="n">.</span><span class="n">author</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">submission</span><span class="n">.</span><span class="n">over_18</span><span class="p">:</span>
            <span class="k">if</span> <span class="s1">'</span><span class="s1">suggestion</span><span class="s1">'</span> <span class="ow">in</span> <span class="n">submission</span><span class="n">.</span><span class="n">title</span><span class="n">.</span><span class="n">lower</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="s1">Found:</span><span class="s1">'</span><span class="p">,</span> <span class="n">submission</span><span class="n">.</span><span class="n">id</span><span class="p">)</span>
</pre></div><p>or group all of the conditions into a single statement or boolean variable:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">submission</span> <span class="ow">in</span> <span class="n">submissions</span><span class="p">:</span>
    <span class="k">if</span> <span class="p">(</span>
        <span class="n">submission</span><span class="n">.</span><span class="n">author</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
        <span class="ow">and</span> <span class="ow">not</span> <span class="n">submission</span><span class="n">.</span><span class="n">over_18</span>
        <span class="ow">and</span> <span class="s1">'</span><span class="s1">suggestion</span><span class="s1">'</span> <span class="ow">in</span> <span class="n">submission</span><span class="n">.</span><span class="n">title</span><span class="n">.</span><span class="n">lower</span><span class="p">(</span><span class="p">)</span>
    <span class="p">)</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="s1">Found:</span><span class="s1">'</span><span class="p">,</span> <span class="n">submission</span><span class="n">.</span><span class="n">id</span><span class="p">)</span>
</pre></div><p>But, with continue, we can achieve the same result in a more simple way:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">submission</span> <span class="ow">in</span> <span class="n">submissions</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">submission</span><span class="n">.</span><span class="n">author</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="k">continue</span>

    <span class="k">if</span> <span class="n">submission</span><span class="n">.</span><span class="n">over_18</span><span class="p">:</span>
        <span class="k">continue</span>

    <span class="k">if</span> <span class="s1">'</span><span class="s1">suggestion</span><span class="s1">'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">submission</span><span class="n">.</span><span class="n">title</span><span class="n">.</span><span class="n">lower</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
        <span class="k">continue</span>

    <span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="s1">Found:</span><span class="s1">'</span><span class="p">,</span> <span class="n">submission</span><span class="n">.</span><span class="n">id</span><span class="p">)</span>
</pre></div><p>Notice that all of the conditions are the opposite of the originals. The mentality changes from "keep only the items with the right properties" to "discard the items with the wrong properties". This usage of continue is often called a "guard clause".</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/continue/continue.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/807fcda09e3f32cccc7b6171794391877b5473cf"><time datetime="2020-03-14T21:41:53-07:00">2020-03-14</time> Add Generators and Continue articles to test the waters.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: d900a927e3757a0c073325bf160bae0f1ba023c0/ifnamemain
title: Python's if __name__ == '__main__'
url: https://voussoir.net/writing/ifnamemain
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="programming.python" href="/writing/tags/python">[python]</a> <a class="tag_link" data-qualname="tutorial" href="/writing/tags/tutorial">[tutorial]</a></p>
<h1>Python's <code>if __name__ == '__main__'</code></h1><ol id="table_of_contents">Table of contents<li><a href="#understanding_if_name_main">Understanding if name == main</a><ol><li><a href="#part_1">Part 1</a></li><li><a href="#part_2">Part 2</a></li><li><a href="#part_3">Part 3</a></li></ol></li><li><a href="#why_doesnt_python_use_a_main_function">Why doesn't Python use a main function</a></li><li><a href="#reallife_examples">Real-life examples.</a></li></ol>
<p>When you read other people's Python source code, you may find something like this at the very bottom of the file:</p>
<div class="highlight python"><pre><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'</span><span class="s1">__main__</span><span class="s1">'</span><span class="p">:</span>
    <span>...</span>
</pre></div><p>This is a construct that is confusing the first time you encounter it. <code>if __name__ == '__main__'</code> is used to make a Python file that is importable by other Python files, as well as executable on its own.</p>
<h2 id="understanding_if_name_main">Understanding if name == main<a class="header_anchor_link" href="#understanding_if_name_main"> (§)</a></h2>
<p>In order to understand how it works and how to use it, there are a few background pieces of information you need to know.</p>
<h3 id="part_1">Part 1<a class="header_anchor_link" href="#part_1"> (§)</a></h3>
<p>When you are writing a program <code>A.py</code> that imports another module <code>B.py</code>, the code inside <code>B.py</code> is executed just like if you ran <code>B.py</code> by itself.</p>
<p>I know that sounds simple, but it's something people don't usually think about when practicing imports for the first time. Here, watch:</p>
<div class="highlight python"><pre><span class="c1"># B.py</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m the B file!</span><span class="s2">"</span><span class="p">)</span>
</pre></div><pre><code>&gt;python B.py
I'm the B file!</code></pre>
<div class="highlight python"><pre><span class="c1"># A.py</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m the A file, and I</span><span class="s2">'</span><span class="s2">m going to import B!</span><span class="s2">"</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">B</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m all done importing B!</span><span class="s2">"</span><span class="p">)</span>
</pre></div><pre><code>&gt;python A.py
I'm the A file, and I'm going to import B!
I'm the B file!
I'm all done importing B!</code></pre>
<p>Suppose you write a file called <code>hextools.py</code> so that you can practice converting between hex and ints.</p>
<div class="highlight python"><pre><span class="c1"># hextools.py</span>
<span class="sd">'''
This library provides functions for converting to and from hex.
'''</span>
<span class="kd">def</span> <span class="nf">int_to_hex</span><span class="p">(</span><span class="narg">x</span><span class="p">)</span><span class="p">:</span>
    <span class="sd">'''
    Python automatically starts it with 0x which we'll strip.
    '''</span>
    <span class="k">return</span> <span class="nb">hex</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="p">[</span><span class="mi">2</span><span class="p">:</span><span class="p">]</span>

<span class="kd">def</span> <span class="nf">hex_to_int</span><span class="p">(</span><span class="narg">h</span><span class="p">)</span><span class="p">:</span>
    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span>

<span class="nb">print</span><span class="p">(</span><span class="n">int_to_hex</span><span class="p">(</span><span class="mi">4000</span><span class="p">)</span><span class="p">)</span>
</pre></div><pre><code>&gt;python hextools.py
fa0</code></pre>
<p>It works great.</p>
<p>Then one day, you need to write a second program that also does some hex-int conversion. Well, instead of copying and pasting those functions, it makes sense to just import hextools.</p>
<div class="highlight python"><pre><span class="c1"># myprogram.py</span>
<span class="kn">import</span> <span class="nn">hextools</span>

<span class="n">x</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="s1">'</span><span class="s1">Please type a number: </span><span class="s1">'</span><span class="p">)</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="s1">'</span><span class="s1">in hex is</span><span class="s1">'</span><span class="p">,</span> <span class="n">hextools</span><span class="n">.</span><span class="n">int_to_hex</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="p">)</span>
</pre></div><p>Now let's run that program.</p>
<pre><code>&gt;python myprogram.py
fa0
Please type a number:</code></pre>
<p>That's annoying — the print statement inside <code>hextools</code> is executed, and shows fa0 before <code>myprogram</code> even asks for input. Well, that's because all the code inside hextools is executed simply by importing it. When I say "all the code", I'm referring to anything that's written on the global scope — anything which touches the left edge of the page. That means the <code>def</code> statements which create the functions and, in this case, the print statement that we left there.</p>
<p>Now you know that importing a file causes that code to run. Hold that thought, it's time for the second piece of background information.</p>
<h3 id="part_2">Part 2<a class="header_anchor_link" href="#part_2"> (§)</a></h3>
<p>Python modules have some special variables in the global scope that are created automatically.</p>
<p>Try starting a python interpreter and calling <code>dir</code>.</p>
<pre><code>&gt;python
&gt;&gt;&gt; dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
&gt;&gt;&gt; __name__
'__main__'</code></pre>
<p>Ok so there's an automatic variable called <code>__name__</code> which is a string that says <code>'__main__'</code> but let's keep going to figure out what it means.</p>
<p>Let's start an interpreter with the <code>-i</code> flag which runs everything in the file and then gives you the repl afterward.</p>
<pre><code>&gt;python -i hextools.py
fa0
&gt;&gt;&gt; dir()
['__annotations__', '__builtins__', '__cached__', '__doc__', '__loader__',
'__name__', '__package__', '__spec__', 'hex_to_int', 'int_to_hex']
&gt;&gt;&gt; __doc__
'\nThis library provides functions for converting to and from hex.\n'
&gt;&gt;&gt; __name__
'__main__'</code></pre>
<p>Well, there's that fa0 again. As you can see, there is a magical variable called <code>__doc__</code> which automatically took the docstring that I wrote at the top of the file. I'm showing that to prove that this is exactly what the variables look like while Python is executing this file. And we see the variable <code>__name__</code> which again says <code>'__main__'</code>.</p>
<p>One more demonstration. Let's check that name variable again, but with an imported module:</p>
<pre><code>&gt;python
&gt;&gt;&gt; import hextools
fa0
&gt;&gt;&gt; __name__
'__main__'
&gt;&gt;&gt; hextools.__doc__
'\nThis library provides functions for converting to and from hex.\n'
&gt;&gt;&gt; hextools.__name__
'hextools'</code></pre>
<p>Aha. So the <code>__name__</code> of my Python repl is <code>'__main__'</code>, but the <code>__name__</code> of an imported module is its name.</p>
<pre><code>&gt;&gt;&gt; import os
&gt;&gt;&gt; os.__name__
'os'
&gt;&gt;&gt; import sys
&gt;&gt;&gt; sys.__name__
'sys'
&gt;&gt;&gt; import requests
&gt;&gt;&gt; requests.__name__
'requests'
&gt;&gt;&gt;
&gt;&gt;&gt; __name__
'__main__'</code></pre>
<p>There is only one module that can be <code>'__main__'</code>, and that's whatever file we run directly. Anything you import will just have its own name.</p>
<h3 id="part_3">Part 3<a class="header_anchor_link" href="#part_3"> (§)</a></h3>
<p>Let's combine these two lessons together. Any code that you write on the global scope will be executed when you import that file into another. But, the magic variable <code>__name__</code> will <strong>only</strong> say <code>'__main__'</code> when you are running that file directly, otherwise it will just say the name of the module.</p>
<div class="highlight python"><pre><span class="c1"># B.py</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m the B file, and my name is</span><span class="s2">"</span><span class="p">,</span> <span class="vm">__name__</span><span class="p">)</span>
</pre></div><div class="highlight python"><pre><span class="c1"># A.py</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m the A file, and I</span><span class="s2">'</span><span class="s2">m going to import B!</span><span class="s2">"</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">B</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m all done importing B!</span><span class="s2">"</span><span class="p">)</span>
</pre></div><pre><code>&gt;python B.py
I'm the B file, and my name is __main__

&gt;python A.py
I'm the A file, and I'm going to import B!
I'm the B file, and my name is B
I'm all done importing B!</code></pre>
<p>This should drive the point home:</p>
<div class="highlight python"><pre><span class="c1"># B.py</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">I</span><span class="s2">'</span><span class="s2">m the B file, and my name is</span><span class="s2">"</span><span class="p">,</span> <span class="vm">__name__</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'</span><span class="s1">__main__</span><span class="s1">'</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="s2">The B file is being run directly!</span><span class="s2">"</span><span class="p">)</span>
</pre></div><pre><code>&gt;python B.py
I'm the B file, and my name is __main__
The B file is being run directly!

&gt;python A.py
I'm the A file, and I'm going to import B!
I'm the B file, and my name is B
I'm all done importing B!</code></pre>
<p>Therefore, by putting code inside an <code>if __name__ == '__main__'</code>, it will only run when you are running that file directly, and it will be skipped if the module is being imported by something else. There's nothing surprising about how this works. It's just an if statement!</p>
<p>Let's apply that change:</p>
<div class="highlight python"><pre><span class="c1"># hextools.py</span>
<span class="sd">'''
This library provides functions for converting to and from hex.
'''</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="kd">def</span> <span class="nf">int_to_hex</span><span class="p">(</span><span class="narg">x</span><span class="p">)</span><span class="p">:</span>
    <span class="sd">'''
    Python automatically starts it with 0x which we'll strip.
    '''</span>
    <span class="k">return</span> <span class="nb">hex</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="p">[</span><span class="mi">2</span><span class="p">:</span><span class="p">]</span>

<span class="kd">def</span> <span class="nf">hex_to_int</span><span class="p">(</span><span class="narg">h</span><span class="p">)</span><span class="p">:</span>
    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'</span><span class="s1">__main__</span><span class="s1">'</span><span class="p">:</span>
    <span class="n">x</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="n">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">int_to_hex</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="p">)</span>
</pre></div><div class="highlight python"><pre><span class="c1"># myprogram.py</span>
<span class="kn">import</span> <span class="nn">hextools</span>

<span class="n">x</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="s1">'</span><span class="s1">Please type a number: </span><span class="s1">'</span><span class="p">)</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="s1">'</span><span class="s1">in hex is</span><span class="s1">'</span><span class="p">,</span> <span class="n">hextools</span><span class="n">.</span><span class="n">int_to_hex</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="p">)</span>
</pre></div><pre><code>&gt;myprogram.py
Please type a number: 42
42 in hex is 2a

&gt;hextools.py 42
2a</code></pre>
<p>Now, <code>hextools.py</code> can be run on its own with an argument on the commandline, and <code>myprogram.py</code> can import hextools and use its functions without interruption.</p>
<p>Great job!</p>
<h2 id="why_doesnt_python_use_a_main_function">Why doesn't Python use a main function<a class="header_anchor_link" href="#why_doesnt_python_use_a_main_function"> (§)</a></h2>
<p>In some languages, like C or Java, all you have to do is define a function called <code>main</code> and it will be run when you execute the file. Why does Python use this somewhat awkward magic string variable instead?</p>
<p>In my opinion it's for a few reasons. Firstly, Python spans a broad range of "program formality" levels. If you just write all your code on the global scope:</p>
<div class="highlight python"><pre><span class="c1"># someprogram.py</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">os</span><span class="n">.</span><span class="n">listdir</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">os</span><span class="n">.</span><span class="n">path</span><span class="n">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="ow">and</span> <span class="n">filename</span><span class="n">.</span><span class="n">endswith</span><span class="p">(</span><span class="s1">'</span><span class="s1">.temp</span><span class="s1">'</span><span class="p">)</span><span class="p">:</span>
        <span class="n">os</span><span class="n">.</span><span class="n">remove</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
</pre></div><p>Then you're basically treating Python like bash/batch (except a million times better). In order to allow for this kind of programming, Python is already predisposed to treat files as a list of instructions to execute in order. Compare that with Java where files are more like baskets full of methods and the order doesn't matter because <code>main</code> is where things actually start running.</p>
<p>Consider also that in Python you can do this:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">f</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="s1">hello</span><span class="s1">'</span><span class="p">)</span>

<span class="n">f</span><span class="p">(</span><span class="p">)</span>

<span class="kd">def</span> <span class="nf">f</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="s1">world</span><span class="s1">'</span><span class="p">)</span>

<span class="n">f</span><span class="p">(</span><span class="p">)</span>
</pre></div><pre><code>hello
world</code></pre>
<p>Which you can't do in a language like C or Java.</p>
<div class="highlight c"><pre><span class="kt">int</span> <span class="nf">f</span><span class="p">(</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">f</span><span class="p">(</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</pre></div><pre><code>main.c:7:5: error: redefinition of 'f'
int f()
    ^
main.c:3:5: note: previous definition is here
int f()
    ^</code></pre>
<div class="highlight java"><pre><span class="kd">class</span> <span class="nc">Main</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">f</span><span class="p">(</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">f</span><span class="p">(</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="n">[</span><span class="n">]</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"</span><span class="s">Hello world!</span><span class="s">"</span><span class="p">)</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></div><pre><code>Main.java:6: error: method f() is already defined in class Main
    public static int f()</code></pre>
<p>And then there's Javascript, which I guess must do a parsing pass before running but doesn't complain about redefines.</p>
<div class="highlight javascript"><pre><span class="kd">function</span> <span class="nx">f</span><span class="p">(</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">f</span><span class="p">(</span><span class="p">)</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">f</span><span class="p">(</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"world"</span><span class="p">)</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">f</span><span class="p">(</span><span class="p">)</span><span class="p">;</span>
</pre></div><pre><code>world
world</code></pre>
<p>But anyway. On the opposite end of the formality spectrum, you can write Python files where everything is tucked away into classes and functions and the file is designed as an import with no regard for command-line usage of that particular file. Well, if Python already has the predisposition to just run all the code on the global scope, why should there be a separate parsing mode, and how would it know which one to use?</p>
<p>That's not to say I love <code>if __name__ == '__main__'</code>. I would prefer to have a separate <code>__main__</code> magic variable that is True or False, then you can just <code>if __main__:</code>. The <code>__name__</code> string is still fine for other purposes, like printing the module to the screen.</p>
<p>As a matter of fact, whenever I upgrade a Python script from quick prototype to reusable code, I always move the main code into a C-like function called <code>main</code> via this template:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">main</span><span class="p">(</span><span class="narg">argv</span><span class="p">)</span><span class="p">:</span>
    <span class="c1"># At this point I usually do my argparse</span>
    <span class="k">return</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'</span><span class="s1">__main__</span><span class="s1">'</span><span class="p">:</span>
    <span class="k">raise</span> <span class="ne">SystemExit</span><span class="p">(</span><span class="n">main</span><span class="p">(</span><span class="n">sys</span><span class="n">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="p">]</span><span class="p">)</span><span class="p">)</span>
</pre></div><p>That's because if you make any variables inside <code>if __name__ == '__main__'</code>, they will be part of the global scope which may have accidental side effects. For example:</p>
<div class="highlight python"><pre><span class="c1"># bad.py</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="kd">def</span> <span class="nf">int_to_hex</span><span class="p">(</span><span class="p">)</span><span class="p">:</span>
    <span class="k">return</span> <span class="nb">hex</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="p">[</span><span class="mi">2</span><span class="p">:</span><span class="p">]</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'</span><span class="s1">__main__</span><span class="s1">'</span><span class="p">:</span>
    <span class="n">x</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="n">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">int_to_hex</span><span class="p">(</span><span class="p">)</span><span class="p">)</span>

<span class="c1"># Great, everything's ready to ship!</span>
</pre></div><p>Spot the error.</p>
<p>Whereas by creating a main function, any variables in <code>main</code> are local variables and are not accessible outside of it.</p>
<h2 id="reallife_examples">Real-life examples.<a class="header_anchor_link" href="#reallife_examples"> (§)</a></h2>
<p>I understand that the examples shown here are very simplistic. Here are some links to my other programs which use ifmain in a purposeful way.</p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoirkit/src/commit/417c14a02338d10a34fdf4875761c9e4a92aef1c/voussoirkit/bytestring.py#L127">bytestring</a> is imported by many of my other programs, but also is very useful on its own.</li>
<li><a href="https://git.voussoir.net/voussoir/epubfile/src/commit/4f44cd642f04f1dc94b1d65c5a52f5f639460276/epubfile.py#L1657">epubfile</a> can be imported into any program that wants to modify epubs, but has a number of builtin utilities that run from ifmain.</li>
</ul>
<p>In a very large project with many files, it's more likely that you'll have a main launcher file, and none of the internals are very useful to run on their own. ifmain essentially bridges the gap to make single Python files useful as imports and standalone utilities.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/ifnamemain/ifnamemain.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/3475cb0abf63172e6392487ffaf41755f71a18e3"><time datetime="2023-09-18T21:01:09-07:00">2023-09-18</time> Replace github links with git.voussoir.net.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/f0ead1ff861217d7cc344f01f32b9abe11aed3f7"><time datetime="2020-03-15T15:12:03-07:00">2020-03-15</time> Possessive it's -&gt; its.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/8efe85d45f745e26868ea69dff0ccb16e138e187"><time datetime="2020-03-15T14:58:36-07:00">2020-03-15</time> Clarify ifmain with better, simpler, A.py B.py examples.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/d900a927e3757a0c073325bf160bae0f1ba023c0"><time datetime="2020-03-15T01:53:28-07:00">2020-03-15</time> Add if __name__ == __main__ tutorial.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 89d52981aabd1def75c527703fcd2a9567c55fa6/counter_counter_thoughts
title: Counter-counter-thoughts
url: https://voussoir.net/writing/counter_counter_thoughts
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="introspection" href="/writing/tags/introspection">[introspection]</a></p>
<h1>Counter-counter-thoughts</h1>
<p>Thought: I should write a post with my thoughts about __<em>blank</em>__.</p>
<p>Counter-thought: The internet is already overflowing with people doing this, my take isn't unique, I don't have any novel information.</p>
<p>Counter-counter-thought: Who cares, it's my blog, text is cheap, maybe someone will find it interesting.</p>
<p>Counter-counter-counter-thought: I am contributing to the slow death of the internet as it is crushed under the weight of 7 billion people who think their thoughts need to be published for some reason.</p>
<p>Here's a hackernews post called <a href="https://news.ycombinator.com/item?id=22800136">What is your blog?</a>. <a href="https://news.ycombinator.com/item?id=22800825">shadowsun7</a> says "My hard rule with the blog is that I should (as much as possible) write only about things I can verify through practice. None of that 'it sounds insightful because it is novel, but actually I came up with it in the shower and I've not actually tested it in real life'" to which <a href="https://news.ycombinator.com/item?id=22803222">_curious_</a> responded "If only more bloggers held the same standard, the internet would be a better place".</p>
<p>Meanwhile, <a href="https://news.ycombinator.com/item?id=22803399">swyx</a> shared his "Learn in public" article, encouraging more publishing and "let the internet correct you when you are inevitably wrong". Does the doing-over-lurking mentality produce better individual results, but at the cost of polluting the internet for everybody else with permanent obelisks of bad early efforts? He even uses the phrase "learning exhaust".</p>
<hr/>
<p>Thought: I should share my thoughts about __<em>blank</em>__.</p>
<p>Counter-thought: "It is better to remain silent and be thought a fool than to speak and remove all doubt"</p>
<p>Counter-counter-thought: Oh please not again.</p>
<p>Counter-counter-counter-thought: "Wise men speak because they have something to say; fools because they have to say something"</p>
<p>Counter-counter-counter-counter-thought: <code>git push</code></p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/counter_counter_thoughts/counter_counter_thoughts.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/6e46f49735861780facd96b8cc9761596e747faa"><time datetime="2020-06-21T22:00:13-07:00">2020-06-21</time> Add "what is your blog" hackernews paragraph.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/7eb5ebe96c4a62ff4565e20214ba496dbe681af7"><time datetime="2020-03-25T14:47:23-07:00">2020-03-25</time> Add quotes about fools speaking.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/89d52981aabd1def75c527703fcd2a9567c55fa6"><time datetime="2020-03-23T16:49:38-07:00">2020-03-23</time> Add counter_counter_thoughts.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 8950928a57a382d2a9653de2a412d6eda1f8c6c8/are_children_better_at_languages
title: Are children better at learning languages?
url: https://voussoir.net/writing/are_children_better_at_languages
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="languagelearning" href="/writing/tags/languagelearning">[languagelearning]</a> <a class="tag_link" data-qualname="life" href="/writing/tags/life">[life]</a></p>
<h1>Are children better at learning languages?</h1><ol id="table_of_contents">Table of contents<li><a href="#inb4">inb4</a></li><li><a href="#introduction_context">Introduction &amp; context</a></li><li><a href="#occams_razor">Occam's razor</a></li><li><a href="#advantages_that_children_have_over_adults">Advantages that children have over adults</a><ol><li><a href="#they_dont_really_have_a_choice">They don't really have a choice</a></li><li><a href="#endless_free_time_and_constant_immersion">Endless free time and constant immersion</a></li><li><a href="#enthusiastic_support_from_day_one">Enthusiastic support from day one</a></li><li><a href="#not_afraid_to_make_mistakes">Not afraid to make mistakes</a></li></ol></li><li><a href="#segue_remember_all_those_spelling_quizzes_from_school">Segue: remember all those spelling quizzes from school</a></li><li><a href="#advantages_that_adults_have_over_children">Advantages that adults have over children</a><ol><li><a href="#ability_to_actively_seek_what_we_want">Ability to actively seek what we want</a></li><li><a href="#we_know_how_to_actively_study">We know how to actively study</a></li><li><a href="#familiarity_with_general_grammar_concepts">Familiarity with general grammar concepts</a></li></ol></li><li><a href="#actionable_takeaways">Actionable takeaways</a></li></ol>
<h2 id="inb4">inb4<a class="header_anchor_link" href="#inb4"> (§)</a></h2>
<p><a href="https://en.wikipedia.org/wiki/Betteridge%27s_law_of_headlines">https://en.wikipedia.org/wiki/Betteridge's_law_of_headlines</a></p>
<h2 id="introduction_context">Introduction &amp; context<a class="header_anchor_link" href="#introduction_context"> (§)</a></h2>
<p>People who learn a new language as a teenager or adult know that it's a very long and difficult process. There comes a time in every language learner's life where they wonder why learning their native language as a child seems to have been comparatively very easy.</p>
<p>There is a popular notion<sup><a href="https://en.wikipedia.org/wiki/Weasel_word">ww</a></sup> that the reason we can learn our native language so well is because childrens' brains are more <em>plastic</em> — pliable, moldable, teachable. While I'm not opposed to believing that children have <em>some</em> level of biological advantage, I think this argument is overblown and serves to be a conversation-stopper more than anything else.</p>
<p>I am not a linguist nor a neuroscientist nor a psychologist. I didn't read any studies before writing this article and don't have any sources to cite. Sorry. The purpose of this article is <a href="/writing/counter_counter_thoughts">not to definitively answer the question</a> but to exercise critical thinking in the face of Claims From The Internet. I want to come up with actionable takeaways instead of just rolling over. I am a layman on this topic and so are you <a class="footnote_link" data-index="1" href="#footnote_text_1" id="footnote_link_1" title="[1] probably">[1]</a>. Even if I had read some neuroscience journal papers, I've heard too much about the flakiness and irreproducibility of psychological studies that I wouldn't actually feel any more confident. I would probably just cite what sounds right to me and ignore the rest. Purists will be annoyed that I can admit this and continue writing anyway. Such is the nature of understanding topics beyond my own expertise.</p>
<p><a class="footnote_text" data-index="1" href="#footnote_link_1" id="footnote_text_1">[1]</a> probably</p>
<h2 id="occams_razor">Occam's razor<a class="header_anchor_link" href="#occams_razor"> (§)</a></h2>
<p>Oh look, it's another explanation of Occam's razor on the internet. Trust me, I wish I didn't have to do this, but there are too many people<sup>ww</sup> who think Occam's razor means "the simplest explanation is probably correct". <a href="https://www.youtube.com/watch?v=4LLny5slQvE">Even Contact gets it wrong</a>, and, as far as I can tell, everything else in that movie is scientifically accurate.</p>
<p>The problem is in the word "simple". If the question is "how do birds fly?" then we could very simply answer it with "magic". In fact, this simple answer could cover just about anything we don't understand. But it's not correct.</p>
<p>A better phrasing of Occam's razor is "one should prefer the explanation that makes the fewest assumptions". To say that birds fly with magic is to make many assumptions: that magic exists, that birds have magic and we don't, but we were able to put that magic into airplanes...?. I also choose the word "prefer" because there's no need to smugly pat myself on the back for picking what's "probably correct" when the whole point is to objectively recognize what I know and don't.</p>
<p>When the question is "why is language learning easy for children but hard for adults", then the answer of neuroplasticity starts to sound like magic, and there is not much in the way of quantifying the difference between child and adult neuroplasticity. The difference is just <em>enough</em> to explain away the question <a class="footnote_link" data-index="2" href="#footnote_text_2" id="footnote_link_2" title="[2] I still haven't cited anything">[2]</a>, but not too much or else adults would be totally incapable of learning anything at all.</p>
<p>Remember, this article is written by a layman using critical thinking to evaluate what I've heard. As such, the remainder of this article lists advantages that children have over adults and vice-versa without resorting to such assumptions.</p>
<p><a class="footnote_text" data-index="2" href="#footnote_link_2" id="footnote_text_2">[2]</a> I still haven't cited anything</p>
<h2 id="advantages_that_children_have_over_adults">Advantages that children have over adults<a class="header_anchor_link" href="#advantages_that_children_have_over_adults"> (§)</a></h2>
<h3 id="they_dont_really_have_a_choice">They don't really have a choice<a class="header_anchor_link" href="#they_dont_really_have_a_choice"> (§)</a></h3>
<p>Learning your first language is pretty much sink or swim, just without the threat of watery death usually associated with sinking and swimming. You've got to admit there's an element of pressure for children to learn a first language. How else will they ask for cookies?</p>
<p>When I am reading or listening to something in Korean and I get frustrated or bored because I don't understand it, I can just turn it off and go do something else in English. There is very little external pressure for me to persevere. Even in foreign language classes, where some teachers strive for 100% target language in the class at all times, of course peers will use English amongst each other. Children don't really have those kinds of alternatives. They only other form of communication they have is crying.</p>
<h3 id="endless_free_time_and_constant_immersion">Endless free time and constant immersion<a class="header_anchor_link" href="#endless_free_time_and_constant_immersion"> (§)</a></h3>
<p>Babies got nothing to do. No work, no school, no obligations whatsoever. If I could sit around listening to my target language all day I'd progress a lot faster too.</p>
<p>An adult learner first has to subtract sleep and responsibilities from their daily allotted 24 hours, then with the remainder has to decide whether learning a language is really important enough to give that time up. Especially because spending X hours on something we're bad at is tougher to do than spending the same X hours on something we're already good at.</p>
<p>It's also important to realize that language is ultimately nothing more than a tool for communication. It's fun to romanticize the idea of learning an interesting language, but that's like romanticizing the idea of building an interesting hammer, which isn't of much use if you don't have anything to nail down. I read this comment recently and it stuck with me:</p>
<blockquote><p>Whenever I hear a native English speaker idly thinking of learning a language for "business reasons," this is the comparison I draw in my head ... it subtly hints at the reality: even with your hard-won language ability, you'll still need to pair it with another skill[set] to get the job. At minimum something like marketing, editing, or translating. No one will pay you to just speak the language.</p>
<p><a href="https://old.reddit.com/user/xanthic_strath">/u/xanthic_strath</a> on <a href="https://old.reddit.com/r/languagelearning/comments/fidbrz/how_useful_is_mandarin_if_you_arent_chinese/fkgvynt/">How useful is Mandarin if you aren't Chinese?</a></p>
</blockquote>
<p>Do you really want to dump hundreds or thousands of your limited life hours into learning the foreign language, only to reach 'competence', when you could be developing your other skills into mastery? You still have to have everything else that makes the language worth using. It's a hard sell.</p>
<p>My desire to learn Korean is purely interest-based, because I enjoy it, so I'm not worried about this part personally. I recognize that the situation is different for people learning English which might be the only means of getting into a career field they want to pursue.</p>
<h3 id="enthusiastic_support_from_day_one">Enthusiastic support from day one<a class="header_anchor_link" href="#enthusiastic_support_from_day_one"> (§)</a></h3>
<p>From the moment a child is born, it's surrounded by a group of people who</p>
<ul>
<li>are enthusiastic about and participate in the child's learning (and put up with their mistakes, see below).</li>
<li>use simple sentences and vocabulary, aka baby talk.</li>
<li>use vocabulary which is relevant to the child's own life.</li>
</ul>
<p>This group is often known as the "family" or so I hear.</p>
<p>Compare that with adult learners who</p>
<ul>
<li>don't have someone holding their hand. Unless you're paying someone or are sufficiently handsome, you won't find a stranger who wants to hear you struggle in your foreign language.</li>
<li>read and listen to native-level material at native-level speed <a class="footnote_link" data-index="3" href="#footnote_text_3" id="footnote_link_3" title="[3] You've probably seen Stephen Krashen demonstrate comprehensible input. It's a good lesson, for sure, but in practicality it's extremely difficult to find material that matches your i+1. Partly because it's hard to define your current i in the first place. The parent-child relationship excels at this in a way that a language teacher or tutor or internet material simply can not. My advice is to not worry about i+1 too too much. I have been listening to TTMIK's Iyagi since nearly the beginning, and although it is still above my skill level, I can feel that my comprehension has been going up. Don't get stuck in an endless search for the perfect i+1 material, just find something that isn't impossible and get started.">[3]</a>.</li>
<li>read and listen to material that isn't actually interesting to them simply for the sake of practicing comprehension.</li>
</ul>
<p>MotivateKorean <a class="footnote_link" data-index="4" href="#footnote_text_4" id="footnote_link_4" title="[4] The channel still exists but he has removed videos of himself and handed the channel over to someone else.">[4]</a> often said that when you're learning vocabulary, there is an important moment where you "make it yours". Discovering an opportunity to use a new word, and succesfully using it, and hearing it come out of your own mouth contributes much more to your learning than just doing flashcards. For children this will occur naturally and frequently in their daily life. For adults, especially those learning on their own in their home country, it won't.</p>
<p><a class="footnote_text" data-index="3" href="#footnote_link_3" id="footnote_text_3">[3]</a> You've probably seen Stephen Krashen demonstrate <a href="https://www.youtube.com/watch?v=fnUc_W3xE1w&amp;t=3m20s">comprehensible input</a>. It's a good lesson, for sure, but in practicality it's extremely difficult to find material that matches your <code>i+1</code>. Partly because it's hard to define your current <code>i</code> in the first place. The parent-child relationship excels at this in a way that a language teacher or tutor or internet material simply can not. My advice is to not worry about <code>i+1</code> too too much. I have been listening to TTMIK's Iyagi since nearly the beginning, and although it is still above my skill level, I can feel that my comprehension has been going up. Don't get stuck in an endless search for the perfect <code>i+1</code> material, just find <em>something</em> that isn't impossible and get started.</p>
<p><a class="footnote_text" data-index="4" href="#footnote_link_4" id="footnote_text_4">[4]</a> The <a href="https://youtube.com/channel/UCBpRNdkIp4LSUCgXOcp1NSQ">channel</a> still exists but he has removed videos of himself and handed the channel over to someone else.</p>
<h3 id="not_afraid_to_make_mistakes">Not afraid to make mistakes<a class="header_anchor_link" href="#not_afraid_to_make_mistakes"> (§)</a></h3>
<p>I was going to make a separate header for "not expected to output for a long time" but let's just combine those thoughts here. When an adult starts learning a language, they'd like to start using it sooner rather than later. Even those who emphasize input-before-output like <a href="https://www.youtube.com/user/MATTvsJapan">Matt vs. Japan</a> <a class="footnote_link" data-index="5" href="#footnote_text_5" id="footnote_link_5" title="[5] he probably would prefer that I link to massimmersionapproach.com first and foremost, but truthfully his youtube channel is a better resource than the site at the moment">[5]</a> understand that adult learners don't want to, and in fact shouldn't, refrain from talking for two years like children do. Children have the advantage that nobody expects them to say anything <a href="https://en.wikipedia.org/wiki/Vocabulary_development#Early_word_learning">for a long time</a>; they get to sit back and soak it up first.</p>
<p>When an adult learner starts texting or talking in their new language, they'd prefer to not make mistakes. Some are more embarrassed about mistakes than others, but I think it's true for all of us. I for one would rather forgo a thought than blunder it.</p>
<p>Of course, mistakes can be very productive because you can get feedback instantly and in context which are pretty much the two best things for feedback to be. It's just very difficult to overcome the pride. See also the previous point about how nobody wants to listen to you stumbling all the time and suddenly I'm even more self-conscious about opening my mouth.</p>
<p><a class="footnote_text" data-index="5" href="#footnote_link_5" id="footnote_text_5">[5]</a> he probably would prefer that I link to <a href="https://massimmersionapproach.com/">massimmersionapproach.com</a> first and foremost, but truthfully his youtube channel is a better resource than the site at the moment</p>
<h2 id="segue_remember_all_those_spelling_quizzes_from_school">Segue: remember all those spelling quizzes from school<a class="header_anchor_link" href="#segue_remember_all_those_spelling_quizzes_from_school"> (§)</a></h2>
<p>The whole premise of this article is an adult trying to figure out why learning a new language is so hard when learning their native language was so easy. So to that end it's important to remember that you had mandatory English classes every single year for, like, twelve years. So when your math classes progressed from arithmetic &gt; algebra &gt; geometry &gt; trig &gt; calculus, and your science classes progressed from biology &gt; chemistry &gt; physics, all the while there was English &gt; English &gt; English &gt; English &gt; English. Literacy rates haven't always been as high as they are now. Children learning to read and write is in fact a big undertaking and not as trivial as it seems in our distant memories.</p>
<p>Just take a look at the vocabulary and expressiveness of a ten year old and remind yourself that <em>that</em> is what ten solid years of uninterrupted native exposure results in. If you can beat that level of comprehension and expressiveness in your foreign language in under ten years (I bet you can), then you're beating the pace you set by learning your native language. I'm currently at 1.5 years of my leisurely pace of Korean studying and I can mostly comprehend teen level text as long as I'm allowed to use a dictionary (listening comp is a different story, but anyway). I know that if I had studied harder over this past year and a half I'd be a lot better, too.</p>
<p>I want to restate this point: It seems to me that learning the second language is <strong>not</strong> actually harder than the first. We just didn't feel and/or don't remember how hard it was and how much help we had the first time around.</p>
<h2 id="advantages_that_adults_have_over_children">Advantages that adults have over children<a class="header_anchor_link" href="#advantages_that_adults_have_over_children"> (§)</a></h2>
<blockquote><p>Brady: You feel so omnipotent when you're with little kids, and you understand things so much better. And we adults are like these gods, and they're trying to understand nuance that they can't possibly understand and you occasionally have these waves of emotions where you're like "I just know so much more than you. I can impart so much knowledge on to you and you don't understand this but I do".</p>
<p>Brady: Like a kid'll go near a lake or something, and you'll be like "don't go near the lake" and you'll stop them from going in the lake. But you know so much more about why that is. You know about drowning and oxygen and swimming and all these things. You have so much more knowledge than them. And they're just these little nothings that know nothing.</p>
<p>Grey: When I'm around little kids, probably the thought that is in my head the most often is "you're just so useless at everything". Like you can't do... you can't do anything. You don't know anything about anything and you can't do anything. And you're just this huge burden on society that we tolerate because eventually you'll grow up and you will be useful. But not today.</p>
<p>Brady: But doesn't that make you feel good because you are useful? You're like "one day you be mighty like me. One day you will be able to buy food. One day you will be able to drive a car. One day you will be able to open a door, but now you can't. I am the god that can do these things". And you and the other adults look at each other knowingly like "we know, we know the stuff. We know how to open a door. They don't".</p>
<p>Grey: I guess I don't derive feelings of superiority by surrounding myself with uselesness.</p>
<p><a href="https://www.hellointernet.fm/podcast/29">Hello Internet #29</a>, around the 1:00:00 mark.</p>
</blockquote>
<h3 id="ability_to_actively_seek_what_we_want">Ability to actively seek what we want<a class="header_anchor_link" href="#ability_to_actively_seek_what_we_want"> (§)</a></h3>
<p>When a very young child is learning their first language, they accumulate vocabulary mostly according to the whims of the world around them: what their family talks about, what's on TV. An older child can <a href="https://www.youtube.com/watch?v=hvrnKrZiqPE" title="Matilda goes to the library - Matilda (1996)">pick out books</a> but will have difficulty really searching for something. The older you get, the more accurately you can search the internet, pick materials for your skill level, ask (well-formed, please!) questions on forums, etc.</p>
<h3 id="we_know_how_to_actively_study">We know how to actively study<a class="header_anchor_link" href="#we_know_how_to_actively_study"> (§)</a></h3>
<p>Earlier I gave the children a point because they get constant immersion for free all the time, but that's passive immersion. Adults can do deliberate study and review and use tools like Anki to make sure that, even when we forget something, we'll get reminded of it in due time.</p>
<h3 id="familiarity_with_general_grammar_concepts">Familiarity with general grammar concepts<a class="header_anchor_link" href="#familiarity_with_general_grammar_concepts"> (§)</a></h3>
<p>Learning a new language requires learning new grammar, obviously, but there are plenty of concepts and patterns that will be the same. When you learn the grammar for expressing "if A then B", you don't have to relearn the entire concept of 'if' as a baby does, you just have to learn the new words for saying it. The same goes for prefixes like anti-/un- or suffixes like -ic/-al and so forth.</p>
<h2 id="actionable_takeaways">Actionable takeaways<a class="header_anchor_link" href="#actionable_takeaways"> (§)</a></h2>
<p>If you're keeping score, you may feel like overall the kids come out on top. Even if an actively studying adult can outpace a child's language learning, the amount of effort and difficulty in reaching fluency makes it feel like more of a slog. However, I think this is better than simply throwing our hands in the air and saying that children just have special plasticy brains and they have the biological advantage and there's nothing we can do about it. Now that I've given my reasons for not believing in that as a primary cause, we can find some actionable takeaways by applying babies' strategies to our own learning.</p>
<ul>
<li><p><strong>They don't really have a choice</strong> → An adult learner should also strive to remove their choices. Stop fleeing back to the English safe zone. Change the language of your phone or computer. This is why Matt vs. Japan calls it the mass immersion approach.</p>
<p>This isn't always conducive to the best work/life/responsibilities balance, but babies don't really have those things so it's not a fair fight. Just try to get as close as you can.</p>
</li>
<li><p><strong>Endless free time and constant immersion</strong> → I know I can't say to just create more free time, but look for ways to increase the time you spend listening. Any time you are not speaking to someone, or doing audio-based work, or forbidden from wearing earbuds, you could be listening.</p>
<p>Remove the <a href="/writing/friction">friction</a> between you and the process of listening. On my phone I use <a href="https://play.google.com/store/apps/details?id=in.krosbits.musicolet&amp;hl=en_US">Musicolet</a> which lets me have one queue for music and one queue for Korean so I can go from not-listening to listening very quickly.</p>
<p>At the moment, my current source of friction is wearing earbuds. They tend to get caught on stuff if outside my shirt, I don't like wearing them inside my shirt, and it is too easy to take them out and not put them back in. I could increase my listening hours by playing Korean on speaker at all times, but I don't for fear of bothering people.</p>
</li>
<li><p><strong>Enthusiastic support from day one</strong> → It's difficult to give any advice here that isn't already commonplace. Find a friend online so you can encourage each other. If they're learning the same language as you, be careful that you don't wind up reinforcing mistakes (because you can't correct each other) or invent phrases that native speakers wouldn't actually use.</p>
</li>
<li><p><strong>Not afraid to make mistakes</strong> → Imagine how would feel if you saw or heard someone make a mistake in English. Would you think they're dumb? No, of course not. So when you make a mistake in your target language, remember that although you might feel embarrassed, the listener probably doesn't mind. And if they do, you should find someone else to talk to.</p>
<p>I won't give too much advice here because I'm still bad at this part. I think I should write more or talk to myself more to practice without the pressure of having a listener. It's dangerous to invent new phrases on your own, but you can safely practice the phrases you already know, get better at saying them out loud, and improve pronunciation.</p>
</li>
</ul>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/are_children_better_at_languages/are_children_better_at_languages.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/932cc5683ecf8001fdea318e8461f4ef9061922f"><time datetime="2022-04-07T11:37:21-07:00">2022-04-07</time> Add MotivateKorean footnote.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/1486c482b617619de918ecf079f40c48c9e10701"><time datetime="2022-01-07T22:45:34-08:00">2022-01-07</time> Add tag life to several articles.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/5647816c868dcacb701cffd083e7133cb8f00e02"><time datetime="2020-09-30T19:38:18-07:00">2020-09-30</time> Fix superscripted hyperlink to accommodate my superscript changes.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/00284747d43a458a3f0841341c5c7761f7486b06"><time datetime="2020-07-29T17:34:02-07:00">2020-07-29</time> Improve and simplify prose.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/82d110ccd998d0b53b4d1451db9240d18afefd6f"><time datetime="2020-03-25T16:58:08-07:00">2020-03-25</time> Link to Friction.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/8950928a57a382d2a9653de2a412d6eda1f8c6c8"><time datetime="2020-03-23T17:02:50-07:00">2020-03-23</time> Add are_children_better_at_languages.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: d32de8d127dba65b11b57c4bc61c58ae7b560fe3/friction
title: Friction
url: https://voussoir.net/writing/friction
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="life" href="/writing/tags/life">[life]</a> <a class="tag_link" data-qualname="introspection" href="/writing/tags/introspection">[introspection]</a></p>
<h1>Friction</h1><ol id="table_of_contents">Table of contents<li><a href="#introduction">Introduction</a></li><li><a href="#homework">Homework</a></li><li><a href="#all_habits">All habits</a></li><li><a href="#my_habits">My habits</a></li></ol>
<h2 id="introduction">Introduction<a class="header_anchor_link" href="#introduction"> (§)</a></h2>
<p>Over the past two years I have become aware of what I now feel is the most important element of habit formation: friction. Recognizing friction has helped me to understand my current habits and form new ones.</p>
<p>I think that all habits can essentially be boiled down into two ingredients:</p>
<ul>
<li><strong>Value</strong> is what you get from the habit. It can be something meaningful like personal growth; it can be fulfilling a necessity or responsibility; it can be as simple as entertainment or enjoyment.</li>
<li><strong>Friction</strong> is everything that <em>prevents, hinders, or slows you</em> from performing the action.</li>
</ul>
<p>More potently, I think we can expect that we will always pick the action which has the best ratio of <code>value/friction</code>.</p>
<h2 id="homework">Homework<a class="header_anchor_link" href="#homework"> (§)</a></h2>
<p>Take homework for example. You're supposed to be doing homework, but getting distracted on the internet is sooo tempting. Let's make a chart:</p>
<p><img alt="" loading="lazy" src="/writing/friction/graph.svg"/></p>
<p>Sure, homework is important. It gives you value in the form of a grade which is important to the rest of your schooling. It's certainly much more important than lazily scrolling the internet. But boy does it have a lot of friction. Maybe it's boring and difficult. Maybe it's math homework with 50 of the same kind of problem that you're sick of doing. Maybe it's an essay on a topic you don't care about and you can't think of anything to write. Procrastinating on the other hand is easy, and in the age of notifications the internet is more than willing to come to you. The ratio of <code>value/friction</code> leans heavily in favor of the internet. I for one have always been a procrastinator.</p>
<p>As the deadline approaches, the homework starts to gain more value — on top of the grade you have "avoiding the shame of not turning anything in", "avoiding the embarrassment of being called on in class and not having an answer", "avoiding a slippery slope trend of not doing my work". If these are enough, the homework will finally get the better ratio and you'll do it. If they're not, well... I assume we've all skipped assignments before. Students who regularly skip homework either aren't finding it to be valuable enough or have too many alternatives with lower friction (or have extenuating circumstances at home, I apologize if that's the case).</p>
<p>So what to do? Clearly I believe that the value of a task <em>can</em> change, but in my experience I don't have much control over it. Short of realizing some benefit that I genuinely didn't know was there, I find value to be generally outside of my control and impossible to give actionable advice for. <em>Just, like, think about how important it is. Just tell yourself to do it</em>. Great thanks. That leaves the other variable: friction.</p>
<p>If you want to build a habit, you should <strong>reduce the friction of that habit</strong> or <strong>increase the friction of the alternatives</strong>.</p>
<p>To continue the homework example, you can increase the friction of internet distractions by putting your cell phone or computer physically far away from you, or study in a room that's far from them — make it so that in order to look at your phone you would have to intentionally get up and do so. At any rate this will certainly stop notifications from stealing you. If your homework requires the computer, try disabling wifi after loading up any webpages that you absolutely need.</p>
<p>Decreasing the friction of homework is not always easy. Try splitting the assignment into multiple parts so that no individual part feels so daunting. For an essay, separate gathering sources, writing your outline, and filling in the body, for example. Anyway I tend to find that once I'm working I'm more willing to continue, so the only thing that matters is reducing the perceived friction at the very beginning.</p>
<p>I'm not in school any more, but when I had essays to write I got into the habit of loading up MS Word, writing my name on the page, and leaving it on my taskbar. It would remain otherwise blank for another two or three days in some cases, but it made me feel happy that I had at least "started", provided a constant reminder to work on it, and reduced the friction of switching into work mode down to a single click. Check out Will Schoder's video <a href="https://www.youtube.com/watch?v=E-F1U4bV2m8">The Closure Effect</a> about the Zeigarnik Effect / open loops / closure. Will's channel is great and I encourage you to watch the rest of it. After you do your homework.</p>
<h2 id="all_habits">All habits<a class="header_anchor_link" href="#all_habits"> (§)</a></h2>
<p>Sorry for talking about homework so much. I think it demonstrates my point in a way that we've all experienced regardless of age or expertise. But make no mistake — I think we can apply this to everything:</p>
<p>Brushing your teeth prevents you from losing them, and only takes a few minutes out of your day.</p>
<p>Eating healthy is ideal, but unhealthy foods are numerous, cheap <a class="footnote_link" data-index="1" href="#footnote_text_1" id="footnote_link_1" title="[1] Meal prepping, buying enough ingredients to make many meals, can bring the per-meal cost down. A singular search found me this Harvard article suggesting that it's still $1.50/day ≈ $45/month more expensive. I imagine this has an air of Vimes's boots to it especially for e.g. overworked single parents, however.">[1]</a>, and tasty <a class="footnote_link" data-index="2" href="#footnote_text_2" id="footnote_link_2" title="[2] I typically hear healthy eaters say that junk food tastes bad to them now. That helps if you're already there! Trusting this can give you confidence (thus value), but taste is undoubtedly a source of friction.">[2]</a>.</p>
<p>Those of you who drink coffee have probably reduced the friction for yourself by setting up automatic morning brews or writing <a href="https://www.evilsocket.net/2016/10/09/IoCOFFEE-Reversing-the-Smarter-Coffee-IoT-machine-protocol-to-make-coffee-using-terminal/#.V_pNbpzrDZ8.reddit">terminal programs</a> to start the machine at a distance. Some people use Keurig-style machines at home, and I think the plastic waste is higher than necessary, but clearly they bring the friction way down.</p>
<p>Programmers know that writing documentation tends to be low on the priority list. Conveniently, just today there were several posts on hackernews about writing documentation in Markdown (<a href="https://news.ycombinator.com/item?id=22675165">one</a>, <a href="https://news.ycombinator.com/item?id=22677161">two</a>, <a href="https://news.ycombinator.com/item?id=22677970">three</a>). Some argue that other formats like AsciiDoc or rst are more powerful (higher value), which is hard to deny, but clearly the <code>value/friction</code> of Markdown has yet to be defeated.</p>
<p>Starting an exercise routine when you don't currently have one is hard. I know because I don't currently have one. I think that most people, once they get themselves to start, find that the value is higher than they expected (beyond fitness, there's sleep quality, stress relief, better appetite) and the friction is lower than they expected (being a little sore isn't the end of the world, you only need to spend a few minutes each day). Probably my biggest source of friction in starting to exercise is confronting the current state of my fitness. As long as I simply don't try, I can imagine my abilities to be as high as I want.</p>
<p>I encourage you to examine the habits in your life, especially the ones you want to get rid of and the ones you want to form. Ask yourself about the value they provide to you, and the sources of friction. Understand that the brain will want to do the things with the fewest steps, the smallest distance, the least lifting.</p>
<p><a class="footnote_text" data-index="1" href="#footnote_link_1" id="footnote_text_1">[1]</a> <a href="https://old.reddit.com/r/MealPrepSunday/">Meal prepping</a>, buying enough ingredients to make many meals, can bring the per-meal cost down. A singular search found me <a href="https://www.hsph.harvard.edu/news/press-releases/healthy-vs-unhealthy-diet-costs-1-50-more/">this Harvard article</a> suggesting that it's still $1.50/day ≈ $45/month more expensive. I imagine this has an air of <a href="https://www.goodreads.com/quotes/72745-the-reason-that-the-rich-were-so-rich-vimes-reasoned">Vimes's boots</a> to it especially for e.g. overworked single parents, however.</p>
<p><a class="footnote_text" data-index="2" href="#footnote_link_2" id="footnote_text_2">[2]</a> I typically hear healthy eaters say that junk food tastes bad to them now. That helps if you're already there! Trusting this can give you confidence (thus value), but taste is undoubtedly a source of friction.</p>
<h2 id="my_habits">My habits<a class="header_anchor_link" href="#my_habits"> (§)</a></h2>
<p>Class is dismissed, you're free to go, but this last portion is for how I have identified <code>value/friction</code> in my own life.</p>
<p>I've never liked using planners or calendars. Partly because having to get a physical thing out of my backpack, and flip to today's date, and not remembering my tasks any time I didn't have it with me was too much burden. And partly because there are some tasks that don't need to have a dated deadline, like finding some movie I heard about and want to watch. During college I started using checklists on my phone <a class="footnote_link" data-index="3" href="#footnote_text_3" id="footnote_link_3" title="[3] Originally I was using the built-in tasks app on my phone which is an LG, but it didn't sync with my computer. Currently I'm using Microsoft's To Do. I am relegating this to a footnote instead of inlining it because I don't actually think it's the greatest option in this space and it lacks many features I want. Like you can print, physically-on-paper print a list but can't export it to json or anything. I just picked it because it's free without ads or premium, isn't totally terrible, and isn't Google, criteria which I find difficult to satisfy on Android. It's made by MS though which isn't great either. Sorry for rambling, I'm battling between my feelings that I should share my current solutions in this article versus my disdain for advertising especially a non-open application by MS.">[3]</a>. Prior to that I rarely used any written tasks and just remembered everything. I've got one list for things that have due dates, ordered by soonest due first, and another list for things without due dates, ordered by most-recently-created first since those ideas will be freshest in my mind and easiest to start. Of course, that list continues to grow faster than I clear it, but scrolling through completed tasks shows I've done quite a lot. Adding emoji helps visually categorize tasks. Meanwhile, there are people who enjoy writing on physical journals so much that that's the better habit for them.</p>
<p>I have been using <a href="https://apps.ankiweb.net/">Anki</a> to keep up on Korean vocabulary for about one year now. I've learned that the best time to do my cards is immediately after I wake up, while I'm still in bed. Not only are my scores highest at this time, but I'm most likely to get the whole day's cards done in a single session. There is no 'second best' time of day to do my cards — if I don't do it in the morning in bed, it's a crapshoot as to when I'll get around to them during the day, usually it takes multiple small sessions, sometimes I don't finish until 1 AM, sometimes I don't finish at all and do extra the next day. I only do about 30 minutes each day so it's not even a big undertaking, but once I'm out of bed I've got too many distractions and friction to get around to Anki.</p>
<p>Learning Korean requires spending a lot of time listening to it. Certainly the long-term value of learning the language is very high for me, but on a day-to-day basis the value of any individual listening session is hard to measure. I can't <em>feel</em> my improvement on such a short timescale, so individual listenings are unfortunately of somewhat low value. Thus it's necessary to reduce my friction as much as possible. That means keeping the tracks on my phone, using <a href="https://play.google.com/store/apps/details?id=in.krosbits.musicolet&amp;hl=en_US">Musicolet</a> which gives me a queue (not playlist!) dedicated to Korean, and putting the Musicolet widget on my home screen so I've got a play button right there. I mentioned this in <a href="/writing/are_children_better_at_languages/#actionable_takeaways">Are children better at learning languages</a>. My current source of friction is earbuds, I wish I could just have it beamed into my brain with no mortal coils to speak of. Those true-wireless bluetooth earbuds seem promising but then you've got the friction of charging them and my experience with bluetooth sound quality hasn't been great, though that might just be from low-quality devices. It would also be nice if Musicolet had widgets for playing particular queues, because the widget now only shows the current queue and you have to open the app to switch them. The next time I get a new phone I think I'll keep this one as a dedicated Korean device with 24/7 airplane mode to save battery and a play button right on the lock screen.</p>
<p>I wrote <a href="https://git.voussoir.net/voussoir/cmd/src/branch/master/gitcheckup.py">gitcheckup.py</a> to check the number of changed files and unpushed commits in each of my important git repositories. The cwd doesn't matter, it always shows me a complete list. I have a tendency to make small or experimental changes that I forget about and go unpushed for a long time. gitcheckup helps me keep my repositories in a clean state by showing me empty checkboxes (the horror!) next to what needs attention <a class="footnote_link" data-index="4" href="#footnote_text_4" id="footnote_link_4" title="[4] This is a good example of tasks that don't go on my regular checklist — it's too dynamic and would be too prone to falling out of sync. I strongly believe in letting the current state of the filesystem act as its own 'checklist' so long as you remove the friction of querying it, that is by writing scripts when necessary. Same goes for my downloads folder, which is perpetually non-empty but I don't need to write &quot;organize downloads&quot; on my checklist because the downloads folder is already it's own checklist — what's there needs to be organized and what's not doesn't. Occasionally I will add a &quot;[tag]&quot; to a filename that needs later re-processing so that by searching my disk with Voidtools Everything, poof, there's a checklist.">[4]</a>.</p>
<p>I have a <a href="https://en.wikipedia.org/wiki/Graphics_tablet">digitizer / drawing tablet</a> that I like to use as a mouse even when I'm not doing any graphics work. It's fun to use and helps me prevent wrist strain from using a regular mouse too long. But when using the tablet, accurately double-clicking is a little harder, and switching from clicking to typing requires dropping the pen in its holder, so there is a bit more friction to using the tablet as a mouse replacement and I'll often instinctively reach for the regular mouse instead. So, I'll occasionally toss my regular mouse to the other side of my desk as a way of forcing the use of the tablet.</p>
<p><a class="footnote_text" data-index="3" href="#footnote_link_3" id="footnote_text_3">[3]</a> Originally I was using the built-in tasks app on my phone which is an LG, but it didn't sync with my computer. Currently I'm using Microsoft's <a href="https://todo.microsoft.com/">To Do</a>. I am relegating this to a footnote instead of inlining it because I don't actually think it's the greatest option in this space and it lacks many features I want. Like you can print, physically-on-paper print a list but can't export it to json or anything. I just picked it because it's free without ads or premium, isn't totally terrible, and isn't Google, criteria which I find difficult to satisfy on Android. It's made by MS though which isn't great either. Sorry for rambling, I'm battling between my feelings that I should share my current solutions in this article versus my disdain for advertising especially a non-open application by MS.</p>
<p><a class="footnote_text" data-index="4" href="#footnote_link_4" id="footnote_text_4">[4]</a> This is a good example of tasks that don't go on my regular checklist — it's too dynamic and would be too prone to falling out of sync. I strongly believe in letting the current state of the filesystem act as its own 'checklist' so long as you remove the friction of querying it, that is by writing scripts when necessary. Same goes for my downloads folder, which is perpetually non-empty but I don't need to write "organize downloads" on my checklist because the downloads folder is already it's own checklist — what's there needs to be organized and what's not doesn't. Occasionally I will add a "[tag]" to a filename that needs later re-processing so that by searching my disk with <a href="https://www.voidtools.com/downloads/">Voidtools Everything</a>, poof, there's a checklist.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/friction/friction.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/3475cb0abf63172e6392487ffaf41755f71a18e3"><time datetime="2023-09-18T21:01:09-07:00">2023-09-18</time> Replace github links with git.voussoir.net.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/0754cda87300919e481a7f50fb03df682f573f1f"><time datetime="2022-04-07T17:23:29-07:00">2022-04-07</time> Fix link to children languages#actionable_takeaways.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/1486c482b617619de918ecf079f40c48c9e10701"><time datetime="2022-01-07T22:45:34-08:00">2022-01-07</time> Add tag life to several articles.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/cc0b264b47448b5eecaf40997ab8da8854c9614a"><time datetime="2020-06-04T10:56:31-07:00">2020-06-04</time> Add svg graph, tweak prose, add digitizer para.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/b25d99f14e9e4433210520e956b01b585d8acc6e"><time datetime="2020-03-30T11:42:14-07:00">2020-03-30</time> Remove bullet points and let these just be paragraphs.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/d32de8d127dba65b11b57c4bc61c58ae7b560fe3"><time datetime="2020-03-25T16:52:47-07:00">2020-03-25</time> Add friction.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 4cbe24584f032af0a0040f5643df8df8e775e65a/argument_packing
title: Argument Packing and Unpacking
url: https://voussoir.net/writing/argument_packing
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="programming.python" href="/writing/tags/python">[python]</a> <a class="tag_link" data-qualname="tutorial" href="/writing/tags/tutorial">[tutorial]</a></p>
<h1>Argument Packing and Unpacking</h1><ol id="table_of_contents">Table of contents<li><a href="#background">Background</a></li><li><a href="#problem">Problem</a></li><li><a href="#solution">Solution</a><ol><li><a href="#positional_packing">Positional Packing</a></li><li><a href="#positional_unpacking">Positional Unpacking</a></li><li><a href="#keyword_packing">Keyword Packing</a></li><li><a href="#keyword_unpacking">Keyword Unpacking</a></li><li><a href="#argument_forwarding">Argument Forwarding</a></li></ol></li></ol>
<h2 id="background">Background<a class="header_anchor_link" href="#background"> (§)</a></h2>
<p>As you know, functions can accept arguments positionally:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="narg">c</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, c=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'</span><span class="s1">hello</span><span class="s1">'</span><span class="p">,</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="p">)</span>
<span>a</span><span>=</span><span>4</span><span>,</span> <span>b</span><span>=</span><span>hello</span><span>,</span> <span>c</span><span>=</span><span>[</span><span>0</span><span>]</span>
</pre></div><p>or by name (aka keyword arguments):</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="narg">c</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="p">,</span> <span class="narg">a</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="narg">b</span><span class="o">=</span><span class="s1">'</span><span class="s1">hello</span><span class="s1">'</span><span class="p">)</span>
<span>a</span><span>=</span><span>4</span><span>,</span> <span>b</span><span>=</span><span>hello</span><span>,</span> <span>c</span><span>=</span><span>[</span><span>0</span><span>]</span>
</pre></div><p>or both, as long as all the keyword arguments come after all the positional arguments:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="narg">c</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="p">,</span> <span class="narg">b</span><span class="o">=</span><span class="s1">'</span><span class="s1">hello</span><span class="s1">'</span><span class="p">)</span>
<span>a</span><span>=</span><span>4</span><span>,</span> <span>b</span><span>=</span><span>hello</span><span>,</span> <span>c</span><span>=</span><span>[</span><span>0</span><span>]</span>
</pre></div><h2 id="problem">Problem<a class="header_anchor_link" href="#problem"> (§)</a></h2>
<p>It would be nice if we could have a function which can accept any number of arguments. For example, a function that adds multiple numbers at once:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span>3</span>
<span>&gt;&gt;&gt;</span> <span class="n">add</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span>
<span>30</span>
</pre></div><p>You could achieve this by giving the function a single parameter, an iterable. Then you can just pass in a list of whatever length you need:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">add</span><span class="p">(</span><span class="narg">numbers</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
<span>...</span>     <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">:</span>
<span>...</span>         <span class="n">total</span> <span class="o">+</span><span class="o">=</span> <span class="n">number</span>
<span>...</span>     <span class="k">return</span> <span class="n">total</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">add</span><span class="p">(</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span><span class="p">)</span>
<span>3</span>
<span>&gt;&gt;&gt;</span> <span class="n">add</span><span class="p">(</span><span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">]</span><span class="p">)</span>
<span>30</span>
</pre></div><p>But this is Python we're talking about, so surely there's a better solu—</p>
<h2 id="solution">Solution<a class="header_anchor_link" href="#solution"> (§)</a></h2>
<p>Right, a better solution. Python offers:</p>
<ul>
<li><p><strong>argument packing</strong>: providing additional arguments into a function call, where they will be packed into a data structure.</p>
</li>
<li><p><strong>argument unpacking</strong>: providing a data structure into a function call, where the elements will be unpacked to fulfill the parameters.</p>
</li>
</ul>
<h3 id="positional_packing">Positional Packing<a class="header_anchor_link" href="#positional_packing"> (§)</a></h3>
<p>With positional packing, we can pass as many positional arguments as we want into the function, and they will be packed into a tuple under a single name:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">*</span><span class="narg">args</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
<span>(</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>3</span><span>,</span> <span>4</span><span>,</span> <span>5</span><span>)</span>
</pre></div><p>The asterisk on <code>*args</code> indicates that <code>args</code> is the packing parameter. In this context, <a href="https://en.wikipedia.org/wiki/Ruby_(programming_language)">Ruby</a> refers to that asterisk as the "splat" operator, or you can just call it "star".</p>
<p>There is not very much magic going on here, it's just a regular tuple:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">*</span><span class="narg">args</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">args</span><span class="p">)</span><span class="p">)</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
<span>(</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>3</span><span>,</span> <span>4</span><span>,</span> <span>5</span><span>)</span>
<span>&lt;</span><span>class</span> <span>'</span><span>tuple</span><span>'</span><span>&gt;</span>
<span>5</span>
</pre></div><p>We can use this to write our addition function:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">*</span><span class="narg">numbers</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
<span>...</span>     <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">:</span>
<span>...</span>         <span class="n">total</span> <span class="o">+</span><span class="o">=</span> <span class="n">number</span>
<span>...</span>     <span class="k">return</span> <span class="n">total</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span>3</span>
<span>&gt;&gt;&gt;</span> <span class="n">add</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span>
<span>30</span>
</pre></div><p>Notice that the star is not part of the variable's name. You don't call it <code>*numbers</code> during the body of the function, only in the header.</p>
<p>Star-args must come after all of the other positional parameters in the definition:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="n">*</span><span class="narg">c</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, c=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>(</span><span>3</span><span>,</span> <span>4</span><span>,</span> <span>5</span><span>)</span>
</pre></div><div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">class</span> <span class="nc">Scalar</span><span class="p">:</span>
<span>...</span>     <span class="kd">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="narg">self</span><span class="p">,</span> <span class="narg">value</span><span class="p">)</span><span class="p">:</span>
<span>...</span>         <span class="bp">self</span><span class="n">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
<span>...</span>     <span class="kd">def</span> <span class="nf">scale</span><span class="p">(</span><span class="narg">self</span><span class="p">,</span> <span class="n">*</span><span class="narg">numbers</span><span class="p">)</span><span class="p">:</span>
<span>...</span>         <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="n">.</span><span class="n">value</span> <span class="o">*</span> <span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">]</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">Scalar</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="n">.</span><span class="n">scale</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span>
<span>[</span><span>8</span><span>,</span> <span>10</span><span>,</span> <span>12</span><span>]</span>
</pre></div><p>If you place any more parameters after star-args, they must be passed in by name (because if you tried to pass them in positionally, they would get lumped into the star-args):</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="n">*</span><span class="narg">c</span><span class="p">,</span> <span class="narg">d</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, c=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s1">, d=</span><span class="si">{</span><span class="n">d</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="narg">d</span><span class="o">=</span><span class="mi">6</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>(</span><span>3</span><span>,</span> <span>4</span><span>,</span> <span>5</span><span>)</span><span>,</span> <span>d</span><span>=</span><span>6</span>
</pre></div><div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">scale</span><span class="p">(</span><span class="n">*</span><span class="narg">numbers</span><span class="p">,</span> <span class="narg">scalar</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="k">return</span> <span class="p">[</span><span class="n">scalar</span> <span class="o">*</span> <span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">]</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">scale</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="narg">scalar</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span>
<span>[</span><span>8</span><span>,</span> <span>16</span><span>,</span> <span>24</span><span>]</span>
</pre></div><h3 id="positional_unpacking">Positional Unpacking<a class="header_anchor_link" href="#positional_unpacking"> (§)</a></h3>
<p>With positional unpacking, we can use the elements of an iterable as the arguments of a function call:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="narg">c</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, c=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">my_args</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="n">my_args</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span>

<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span>

<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span>
</pre></div><div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">double_sides</span><span class="p">(</span><span class="narg">width</span><span class="p">,</span> <span class="narg">height</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="k">return</span> <span class="p">(</span><span class="n">width</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">height</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">a</span> <span class="o">=</span> <span class="n">double_sides</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
<span>&gt;&gt;&gt;</span> <span class="n">a</span>
<span>(</span><span>6</span><span>,</span> <span>8</span><span>)</span>
<span>&gt;&gt;&gt;</span> <span class="n">a</span> <span class="o">=</span> <span class="n">double_sides</span><span class="p">(</span><span class="o">*</span><span class="n">a</span><span class="p">)</span>
<span>&gt;&gt;&gt;</span> <span class="n">a</span>
<span>(</span><span>12</span><span>,</span> <span>16</span><span>)</span>
</pre></div><p>You can pass arguments in normally on either side:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span>
</pre></div><p>If the iterable is not long enough, you can also specify the rest by name:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span><span class="p">,</span> <span class="narg">c</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span>
</pre></div><p>If the iterable is too long, you will of course get a TypeError for passing in too many arguments. But if the function has a star-args, what do you think will happen?</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="n">*</span><span class="narg">c</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, c=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>(</span><span>3</span><span>,</span> <span>4</span><span>,</span> <span>5</span><span>,</span> <span>6</span><span>)</span>
</pre></div><p>Did you know that the splat operator can take the elements of a nested list and make them elements of the parent list?</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span><span class="p">]</span>
<span>[</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>3</span><span>]</span>
</pre></div><p>Therefore:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="narg">c</span><span class="p">,</span> <span class="narg">d</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, c=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s1">, d=</span><span class="si">{</span><span class="n">d</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="o">*</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span><span class="p">]</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>c</span><span>=</span><span>3</span><span>,</span> <span>d</span><span>=</span><span>4</span>
</pre></div><p>(I'm not saying you should actually do this)</p>
<h3 id="keyword_packing">Keyword Packing<a class="header_anchor_link" href="#keyword_packing"> (§)</a></h3>
<p>With keyword packing, we can pass as many keyword arguments as we want into the function, and they will be placed into a dictionary with the argument names as strings. We use two stars for this:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="n">*</span><span class="n">*</span><span class="narg">kwargs</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="n">kwargs</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="narg">a</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="narg">b</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="narg">c</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
<span>{</span><span>'</span><span>a</span><span>'</span><span>:</span> <span>1</span><span>,</span> <span>'</span><span>b</span><span>'</span><span>:</span> <span>2</span><span>,</span> <span>'</span><span>c</span><span>'</span><span>:</span> <span>3</span><span>}</span>
</pre></div><p>Star-star-kwargs must come after all the other named arguments in the definition. The kwargs dict will only be used for arguments that aren't explicitly defined:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="n">*</span><span class="n">*</span><span class="narg">kwargs</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, kwargs=</span><span class="si">{</span><span class="n">kwargs</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="narg">a</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="narg">b</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="narg">c</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>kwargs</span><span>=</span><span>{</span><span>'</span><span>c</span><span>'</span><span>:</span> <span>3</span><span>}</span>
</pre></div><p>In <a href="#argument_forwarding">Argument Forwarding</a> I will show the most common usage of kwargs. Until then, there are not very many situations where I use it. I guess you could have a "secret menu" of arguments that are supported but not shown in the function's parameter list:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">unassuming_storefront</span><span class="p">(</span><span class="n">*</span><span class="n">*</span><span class="narg">order</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="k">if</span> <span class="s1">'</span><span class="s1">wink_wink_nudge_nudge</span><span class="s1">'</span> <span class="ow">in</span> <span class="n">order</span><span class="p">:</span>
<span>...</span>         <span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="s1">Say no more</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">unassuming_storefront</span><span class="p">(</span><span class="narg">wink_wink_nudge_nudge</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span>Say</span> <span>no</span> <span>more</span>
</pre></div><h3 id="keyword_unpacking">Keyword Unpacking<a class="header_anchor_link" href="#keyword_unpacking"> (§)</a></h3>
<p>With keyword unpacking, we can use the (key, value) pairs of a dictionary as the arguments of a function call:</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">my_args</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'</span><span class="s1">a</span><span class="s1">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'</span><span class="s1">b</span><span class="s1">'</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="o">*</span><span class="n">my_args</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span>
</pre></div><p>And if the function also has a kwargs?</p>
<div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kd">def</span> <span class="nf">example</span><span class="p">(</span><span class="narg">a</span><span class="p">,</span> <span class="narg">b</span><span class="p">,</span> <span class="n">*</span><span class="n">*</span><span class="narg">kwargs</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'</span><span class="s1">a=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s1">, b=</span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s1">, kwargs=</span><span class="si">{</span><span class="n">kwargs</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">example</span><span class="p">(</span><span class="o">*</span><span class="o">*</span><span class="p">{</span><span class="s1">'</span><span class="s1">a</span><span class="s1">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'</span><span class="s1">b</span><span class="s1">'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'</span><span class="s1">c</span><span class="s1">'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'</span><span class="s1">d</span><span class="s1">'</span><span class="p">:</span> <span class="mi">4</span><span class="p">}</span><span class="p">)</span>
<span>a</span><span>=</span><span>1</span><span>,</span> <span>b</span><span>=</span><span>2</span><span>,</span> <span>kwargs</span><span>=</span><span>{</span><span>'</span><span>c</span><span>'</span><span>:</span> <span>3</span><span>,</span> <span>'</span><span>d</span><span>'</span><span>:</span> <span>4</span><span>}</span>
</pre></div><p>Once again, I don't typically use this feature on its own, and I'm assuming you understand how this works by now. So let's move on to the most common usage for <code>*args</code> and <code>**kwargs</code>.</p>
<h3 id="argument_forwarding">Argument Forwarding<a class="header_anchor_link" href="#argument_forwarding"> (§)</a></h3>
<p>Consider an API wrapper which makes web requests. Behind the scenes there is probably a <code>request</code> function:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">request</span><span class="p">(</span><span class="narg">url</span><span class="p">)</span><span class="p">:</span>
    <span class="c1"># does some Internet magic.</span>

<span class="kd">def</span> <span class="nf">get_comments</span><span class="p">(</span><span class="narg">user</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/comments.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

<span class="kd">def</span> <span class="nf">get_submissions</span><span class="p">(</span><span class="narg">user</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/submissions.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</pre></div><p>One day we decide to add a new argument on the core <code>request</code> function. Suppose we want to give our requests a timeout:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">request</span><span class="p">(</span><span class="narg">url</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span><span class="p">:</span>
    <span class="c1"># does some Internet magic.</span>

<span class="kd">def</span> <span class="nf">get_comments</span><span class="p">(</span><span class="narg">user</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/comments.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

<span class="kd">def</span> <span class="nf">get_submissions</span><span class="p">(</span><span class="narg">user</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/submissions.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</pre></div><p>Of course, when people use our API wrapper, they don't call <code>request</code> directly, they call the other functions. So in order to use the new timeout feature, we'll have to add a <code>timeout</code> parameter to all the functions which call <code>request</code> so they can pass it along:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">request</span><span class="p">(</span><span class="narg">url</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span><span class="p">:</span>
    <span class="c1"># does some Internet magic.</span>

<span class="kd">def</span> <span class="nf">get_comments</span><span class="p">(</span><span class="narg">user</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/comments.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="n">timeout</span><span class="p">)</span>

<span class="kd">def</span> <span class="nf">get_submissions</span><span class="p">(</span><span class="narg">user</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/submissions.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="n">timeout</span><span class="p">)</span>
</pre></div><p>Great, now every time we add a new parameter to <code>request</code> we have to go copy-paste it to every other function in the entire library. Not to mention it seriously clutters up all the function definitions and may distract you from the important parameters those functions have.</p>
<p>It works, but it ain't cool. And why would I want to do anything that's not cool?</p>
<p>Instead, let's take advantage of args and kwargs:</p>
<div class="highlight python"><pre><span class="kd">def</span> <span class="nf">request</span><span class="p">(</span><span class="narg">url</span><span class="p">,</span> <span class="narg">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span><span class="p">:</span>
    <span class="c1"># does some Internet magic.</span>

<span class="kd">def</span> <span class="nf">get_comments</span><span class="p">(</span><span class="narg">user</span><span class="p">,</span> <span class="n">*</span><span class="narg">request_args</span><span class="p">,</span> <span class="n">*</span><span class="n">*</span><span class="narg">request_kwargs</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/comments.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">*</span><span class="n">request_args</span><span class="p">,</span> <span class="o">*</span><span class="o">*</span><span class="n">request_kwargs</span><span class="p">)</span>

<span class="kd">def</span> <span class="nf">get_submissions</span><span class="p">(</span><span class="narg">user</span><span class="p">,</span> <span class="n">*</span><span class="narg">request_args</span><span class="p">,</span> <span class="n">*</span><span class="n">*</span><span class="narg">request_kwargs</span><span class="p">)</span><span class="p">:</span>
    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">https://example.com/user/</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">/submissions.json</span><span class="s1">'</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">*</span><span class="n">request_args</span><span class="p">,</span> <span class="o">*</span><span class="o">*</span><span class="n">request_kwargs</span><span class="p">)</span>
</pre></div><p>In this case, the end result is a little longer. But now, no matter how many new positional or keyword parameters we add to <code>request</code>, we don't have to touch the other functions ever again. Any arguments that aren't part of their own definition will simply be forwarded along to <code>request</code>.</p>
<p>If <code>request</code> uses any other internal functions behind the scenes (probably <code>urllib</code>), it might even forward some arguments there.</p>
<p>Argument forwarding is a staple of writing decorators. The wrapped function needs to support all of the args and kwargs of the original function without even knowing what they are!</p>
<div class="highlight python"><pre><span class="kn">import</span> <span class="nn">functools</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="kd">def</span> <span class="nf">timed</span><span class="p">(</span><span class="narg">function</span><span class="p">)</span><span class="p">:</span>
    <span class="nd">@functools</span><span class="nd">.</span><span class="nd">wraps</span><span class="p">(</span><span class="nd">function</span><span class="p">)</span>
    <span class="kd">def</span> <span class="nf">wrapped</span><span class="p">(</span><span class="nd">*</span><span class="narg">args</span><span class="p">,</span> <span class="nd">*</span><span class="nd">*</span><span class="narg">kwargs</span><span class="p">)</span><span class="p">:</span>
        <span class="nd">start</span> <span class="o">=</span> <span class="nd">time</span><span class="nd">.</span><span class="nd">time</span><span class="p">(</span><span class="p">)</span>
        <span class="nd">return_value</span> <span class="o">=</span> <span class="nd">function</span><span class="p">(</span><span class="o">*</span><span class="nd">args</span><span class="p">,</span> <span class="o">*</span><span class="o">*</span><span class="nd">kwargs</span><span class="p">)</span>
        <span class="nd">end</span> <span class="o">=</span> <span class="nd">time</span><span class="nd">.</span><span class="nd">time</span><span class="p">(</span><span class="p">)</span>
        <span class="nd">elapsed</span> <span class="o">=</span> <span class="nd">end</span> <span class="o">-</span> <span class="nd">start</span>
        <span class="nb">print</span><span class="p">(</span><span class="nd">function</span><span class="p">,</span> <span class="s1">'</span><span class="s1">took </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="nd">elapsed</span><span class="p">)</span>
        <span class="k">return</span> <span class="nd">return_value</span>
    <span class="k">return</span> <span class="nd">wrapped</span>
</pre></div><div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">hashlib</span>
<span>&gt;&gt;&gt;</span> <span class="nd">@timed</span>
<span>...</span> <span class="kd">def</span> <span class="nf">hash_it</span><span class="p">(</span><span class="narg">data</span><span class="p">)</span><span class="p">:</span>
<span>...</span>     <span class="k">return</span> <span class="n">hashlib</span><span class="n">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="n">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="p">)</span>
<span>...</span>
<span>&gt;&gt;&gt;</span> <span class="n">hash_it</span><span class="p">(</span><span class="sa">b</span><span class="s1">'</span><span class="s1">hello</span><span class="s1">'</span><span class="p">)</span>
<span>&lt;</span><span>function</span> <span>hash_it</span> <span>at</span> <span>0x04BF58E8</span><span>&gt;</span> <span>took</span> <span>0.0005013942718505859</span>
<span>'</span><span>2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824</span><span>'</span>
</pre></div><p>It's also very useful for object inheritance, where the child class has some specific parameters and the rest of them get used in the super constructor:</p>
<div class="highlight python"><pre><span class="kd">class</span> <span class="nc">DatabaseObject</span><span class="p">:</span>
    <span class="kd">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="narg">self</span><span class="p">,</span> <span class="narg">db_connection</span><span class="p">)</span><span class="p">:</span>
        <span class="bp">self</span><span class="n">.</span><span class="n">connection</span> <span class="o">=</span> <span class="n">db_connection</span>

<span class="kd">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">DatabaseObject</span><span class="p">)</span><span class="p">:</span>
    <span class="kd">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="narg">self</span><span class="p">,</span> <span class="narg">username</span><span class="p">,</span> <span class="n">*</span><span class="narg">args</span><span class="p">,</span> <span class="n">*</span><span class="n">*</span><span class="narg">kwargs</span><span class="p">)</span><span class="p">:</span>
        <span class="nb">super</span><span class="p">(</span><span class="p">)</span><span class="n">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">*</span><span class="o">*</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="bp">self</span><span class="n">.</span><span class="n">username</span> <span class="o">=</span> <span class="n">username</span>
</pre></div><div class="highlight python"><pre><span>&gt;&gt;&gt;</span> <span class="n">u</span> <span class="o">=</span> <span class="n">User</span><span class="p">(</span><span class="narg">db_connection</span><span class="o">=</span><span class="s1">'</span><span class="s1">just pretend!</span><span class="s1">'</span><span class="p">,</span> <span class="narg">username</span><span class="o">=</span><span class="s1">'</span><span class="s1">exampleman</span><span class="s1">'</span><span class="p">)</span>
<span>&gt;&gt;&gt;</span> <span class="n">u</span><span class="n">.</span><span class="n">username</span>
<span>'</span><span>exampleman</span><span>'</span>
<span>&gt;&gt;&gt;</span> <span class="n">u</span><span class="n">.</span><span class="n">connection</span>
<span>'</span><span>just pretend!</span><span>'</span>
</pre></div>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/argument_packing/argument_packing.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/4cbe24584f032af0a0040f5643df8df8e775e65a"><time datetime="2020-03-27T19:11:40-07:00">2020-03-27</time> Add argument_packing.md</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: c9ec853d892b45aa30d87897a2c6123ccfff44a0/cyborgs_on_hn
title: Cyborgs on HN
url: https://voussoir.net/writing/cyborgs_on_hn
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<h1>Cyborgs on HN</h1>
<p>This page collects comments which make unnecessary or tenuous analogies to computers, programming, dollar-sign $variables, sed's/replace/syntax/g, mathematics, AI/machine learning, and cryptography in discussions that aren't about those things.</p>
<blockquote><p>Agreed, but that's a two part issue.</p>
<p>#1 - The modern web has a much lower ratio of citizenCreators : consumers than the early web.</p>
<p>#2 - The utility of information is professionalUnbiased &gt; proHobbyist &gt; professionalBiased &gt; randomPerson &gt;&gt; SEO spam.</p>
<p>Google's current ranking is SEO spam &gt;&gt; professionalBiased &gt; professionalUnbiased &gt;&gt; proHobbyist &gt; randomPerson.</p>
<p>That's a clear misalignment.</p>
</blockquote>
<blockquote><p>Sadly, due to being on the first floor* I don't have a lawn.</p>
<p>I do keep considering balcony mounts for what would've been the lawn defence cannon though.</p>
<p>* en_UK first floor, mind, I know en_US insists on 1-based indexing rather than 0-based for floors but I don't particularly like writing FORTRAN either.</p>
</blockquote>
<blockquote><p>s/at the speed of light/no faster than the speed of light/</p>
</blockquote>
<blockquote><p>Also, what is with this misconception: "When twins aren’t being regarded as carbon copies, they are slotted (or slot themselves) into opposing roles." Dude, any vector can be projected onto another one, with an orthogonal ("opposing") component left over.</p>
</blockquote>
<blockquote><p>Layoffs in the WFH era are weird. Back in the day you had a pretty good idea of who got laid off because you saw them walking out the door with a box of their stuff. You could go up to them and say, "hey let's meet at $local_watering_hole and hang out". You could swap contact info if you didn't already have it.</p>
</blockquote>
<blockquote><p>On far older hardware, we still used spinning rust for $deity sake...</p>
</blockquote>
<blockquote><p>Once you understand the embryology, there are actually a variety of things like the appendix: they're just the ends of things that come together. The uvula is a great example: embryologically, the two sides of the face separate very early as an opening, then develop a lot of complicated anatomy and fuse back together. The uvula is where that fusion completes and the fusion process completes (returns 0) when the microenvironment variables get to a certain state.</p>
</blockquote>
<blockquote><p>The ability to shutdown any persons the government doesn't like using any growing list of $Excuses is worrying.</p>
</blockquote>
<blockquote><p>I have a couple of examples (of HN and high-ranking HNers natch ;) and found a fair number of bugs^H^H^H^H non-optimal choices in their feeds, probably because XML is not all that human-readable, even for devs.</p>
</blockquote>
<blockquote><p>I will admit, I've often searched for $project_name github to get to their repositories. It shouldn't matter but it's just a force of habit now.</p>
</blockquote>
<blockquote><p>Xiaomi is China's Apple, so what happened to the rumored Apple car? Haven't heard anything for a while, but Wikipedia claims it's still planned for 2024-25:</p>
<blockquote><p>Yeah, under the hood that date is {{Date().getFullYear() + 1}}</p>
</blockquote>
<blockquote><p>Calling it: Apple will partner with an established automaker such as Goldman Sachs^H^H^H^H^H^H^H^H^H^H^H^H^HPolestar and customize the infotainment OS.</p>
</blockquote>
</blockquote>
<blockquote><p>Inc actually does this sort of madlibs article all the time</p>
<p>"In $NUMBER (words/sentences), $FOO taught a masterclass in $BAR"</p>
</blockquote>
<blockquote><p>You're positing that some significant portion of the victims decide to settle like this:</p>
<p>if (shame)</p>
<p>But it's almost certainly this:</p>
<p>if (bankruptcy_risk || shame)</p>
</blockquote>
<blockquote><p>I actually don't benefit at all from you using my code; I did all the work to make the world a place where $PROBLEM has an accessible solution.</p>
</blockquote>
<blockquote><p>At $DAY_JOB we recently scuttled most development efforts for a week for our teams. Our nightly backup job that sanitizes PHI ballooned overtime to, say, 20GB+1Byte and ran out of disk space. Because we are running Kubernetes on Fargate we don’t need a full time operations guy, right?</p>
</blockquote>
<blockquote><p>I'm ashamed to admit I often wonder about this when playingˆHˆHˆHˆHˆHˆHˆH my daughter plays with myˆHˆH her Duplo train.</p>
</blockquote>
<blockquote><p>I have no dog in this fight as I rarely deal with cash and do not run a retail business. However from a conceptual point of view I really have a viscerally negative reaction to not accepting cash. It is a societal acceptance of the rent seeking behavior of the credit processing networks. I already have to pay a sales tax to ${TAX_ENTITIES.length} different government entities, I don't love that I also have a 3% tax going to VISA/Mastercard/Amex/...</p>
</blockquote>
<blockquote><p>Anyone who uses ChatGPT for a while will learn to take everything with a grain of salt.</p>
<blockquote><p>s/Grain/huge rock/</p>
</blockquote>
</blockquote>
<blockquote><p>Addendum: don’t rely on your git/wiki/document-system for tracking the timestamp. "This was accurate on $DATE" has a very different meaning than "this document was last edited on $DATE". It might be 2 years out of date but someone fixed a typo last week.</p>
</blockquote>
<blockquote><p>or s/bottled water/water bottles</p>
</blockquote>
<blockquote><p>Also, adding a charger for your swappable-battery car costs pennies, so lots of people won't even use their car's battery a swapping feature (which is really bad for battery swapping outlets). If most people only use the battery-swap at Christmas and $HOLIDAY, then the networks could be underprovisioned during those spikes and overprovisioned in the other 363 days of the year.</p>
</blockquote>
<blockquote><p>Horrible edge cases to consider when dealing with music</p>
<blockquote><p>title += " publishing metadata"</p>
</blockquote>
</blockquote>
<blockquote><p>That video is wrong. Men who are actually friends don't tell each other "that's a nice tie", they tell each other "nice tie $SLUR".</p>
</blockquote>
<blockquote><p>struggles with too many files. Last I looked, official Microsoft documentation says to not exceed 100k files. Yet, $WORK wants to dump basically every file I touch inside there. My Teams folder alone is ~20k files.</p>
</blockquote>
<blockquote><p>Tuta became understandably a walled garden that my other tools couldn't easily work with. This wasted alot of my time.</p>
<p>Nowadays using $normalemailprovider with Thunderbird.</p>
</blockquote>
<blockquote><p>But having a favourite release of Mac OSX means it's Snow Leopard. I've seen comparisons between $current and $current_minus_one, but never in regards to "That was my favourite" - likely because anyone invested enough in Mac OS releases to have a favourite, favours Snow Leopard.</p>
</blockquote>
<blockquote><p>Sure. But the $CURRENT_MODEL's price never decreases. It stays steady - until the next price bump. E.g. $MID_RANGE_GPU, $HIGH_END_CPU - their price will decrease next year, when the new model comes out. The new model's price will not be lower than the current one.</p>
</blockquote>
<blockquote><p>I now guess somebody wrote it up, published it and maybe linked to it on Twitter, some content-farming bots notice $ACTRESS_NAME trending on Twitter, and their algorithm realize there's audience eyeballs to be gained there, and found the freshest article about her, on Twitter I suppose?</p>
</blockquote>
<blockquote><p>s/China's TikTok users/Users of China's TikTok/</p>
</blockquote>
<blockquote><p>Also starting to realise that my "they can't stop me gaming when I'm 80!" attitude will be self-limited by joint pain at some point... probably in exactly the same way as previous generations, just s/gaming/popular activity of the era/</p>
</blockquote>
<blockquote><p>Calm down satan. Until you manage to manufacture physical products for free (one time cost then get inf copies, as digital content) retail theft !== digital pirating</p>
</blockquote>
<blockquote><p>From someone that occasionally visits YouTube, hearing people talk about YouTube this and YouTube that numerous times in one conversation let alone in a day is very noticeable. Same thing goes for Reddit. Or TikTok. Or any $socialMediaPlatform.</p>
</blockquote>
<blockquote><p>It's been a while now since I used a self-checkout, but my general philosophy is "Sorry, $Employer pays me to fight with buggy, UI-from-hell computers at $Job. If you want me to fight with your self-checkout system, my rate is \$$Lots/hour".</p>
</blockquote>
<blockquote><p>s/Spending/Stocking Up/</p>
</blockquote>
<blockquote><p>I'd pinged HN mods about this, though dang ended up dong a wholesale replacement rather than s/She/Sabine Hossenfelder/.</p>
</blockquote>
<blockquote><p>It really does sound weird. I already imagine trying to sell this at my $DAYJOB (I would likely have to "fork" it myself just to replace a few strings in the codebase that mention the original name).</p>
</blockquote>
<blockquote><p>Ask HN: What programming languages are you using at $WORK?</p>
</blockquote>
<blockquote><p>If you search "watch $MOVIE free" on google you're going to get netflix, Hulu, prime, Disney etc as the first results regardless of whether those sites even have it in their library.</p>
</blockquote>
<blockquote><p>I hope these journalists are ashamed of themselves and I hope they are at least sacked, with their careers relegated to selling poutine all day.</p>
<blockquote><p>s/selling/eating/ ?</p>
</blockquote>
</blockquote>
<blockquote><p>There is always plenty wrong with each new release but the comments and jokes about ${"LATEST_RELEASE"} of ${"SOFTWARE"} being unilaterally bad just make me think the person can't deal with change instead of the software actually being bad.</p>
</blockquote>
<blockquote><p>I'm scratching my head here. The old gas/diesel dispensing stations have solved this problem of restricting people taking all of the fuel in the pump with a disruptive financial technology called blo^H^H^H credit card.</p>
</blockquote>
<blockquote><p>With that said, it's been a reliable solution and does not need me or my friends to create $BIGTECH account.</p>
</blockquote>
<blockquote><p>I am endlessly annoyed by slow interfaces. At $DAYJOB I have to use a web and desktop GUI for managing CheckPoint firewalls. These often will freeze for dozens of seconds, and generally make my computer crawl. I feel that this should not be acceptable in 2023.</p>
</blockquote>
<blockquote><p>Quite. s/"thanks to abusive users"/"thanks to an abusive provider".</p>
</blockquote>
<blockquote><p>So Dutch is little-endian?</p>
</blockquote>
<blockquote><p>s/world/US</p>
</blockquote>
<blockquote><p>God I hate the business models of $CURRENT_YEAR</p>
</blockquote>
<blockquote><p>If you don't care about the notifications anyways....then turn them off/silence them. Switch from an event-based model to a polling one, since you're treating it that way anyways.</p>
</blockquote>
<blockquote><p>I don't live in a country where it's as one-dimensional as: (public road) ⇒ ¬(privacy rights) and I value that.</p>
</blockquote>
<blockquote><p>An "Architect" was pushing SMS 2FA for our application, and only SMS 2FA. Like you I pointed out all the issues. The response I got was "well if it's good enough for $BIGCORP it is good enough for us" and I didn't win. Cargo culting at it's finest.</p>
</blockquote>
<blockquote><p>Imagine scraping by to pay rent from your minimum wage job, and you're told that you can't sign up for $SERVICE because you don't have a new enough phone or a yubikey.</p>
</blockquote>
<blockquote><p>s/Unfortunately/Thankfully/ but a great point!</p>
</blockquote>
<blockquote><p>Just FYI, discord.io !== discord.com the chat app, it's a related but separate service.</p>
</blockquote>
<blockquote><p>s/receives/sentenced to/</p>
</blockquote>
<blockquote><p>"Receives $X years" Is an idiomatic way of expressing prison terms.</p>
</blockquote>
<blockquote><p>There are many websites that provide exactly that function. I suspect most of them are based yt-dl{,p}.</p>
</blockquote>
<blockquote><p>Reading 3 books on $topic will give you a deeper understanding and appreciation for $topic than 99.9% of the population. Yet you are still at basically zero compared to the people who wrote those books.</p>
</blockquote>
<blockquote><p>It was one of her tweets, it's on wikipedia and was widely "reported" at the time. It came in the context of her late conversion to Islam, and the blowback it generated (imagine being a high-profile personality in a deeply $religion-following country, and publicly abiuring that to become $aVeryDifferentReligion...).</p>
</blockquote>
<blockquote><p>I don't think the author is trying to spur you to action. Like most articles, it's meant to make you think.</p>
<blockquote><p>s/think/click/.</p>
</blockquote>
</blockquote>
<blockquote><p>"We switched to $BUZZWORD and reduced $LOOSELY_UNDERSTOOD_METRIC by $STIMULATING_NUMBER !"</p>
<p>Never tell them it's just because of all the technical debt you finally had organizational will to pay off.</p>
</blockquote>
<blockquote><p>We've had great luck at $CURJOB with forum software.</p>
</blockquote>
<blockquote><p>Why have a geofilter if the vast majority of people are logging into the site to see if anyone is talking shit about $COMPANY and the only way to make money is if $COMPANY pays you off to take those posts down?</p>
</blockquote>
<blockquote><p>&gt; On a PC laptop it often is. Even on many all in ones now.</p>
<p>s/even/especially/</p>
</blockquote>
<blockquote><p>Facebook^W Meta is scum.</p>
</blockquote>
<blockquote><p>People who don't own $valuable_asset believe that it should be regulated and there should be mechanisms in place so that everyone has some $valuable_asset.</p>
</blockquote>
<blockquote><p>How does one go about getting such numbers? Do a web search for "$BANK_NAME virtual credit card number"?</p>
</blockquote>
<blockquote><p>It's supposedly a mere slip slope from there to every restaurant and grocer in town refusing to sell food to members of $Disadvantaged_Group, causing them to die of starvation.</p>
</blockquote>
<blockquote><p>But no, now that the market has been oligopolized, it's more profitable to show you what you don't want, and you'll have to be satisfied with whatever incidental successes you manage while wading through the output of the Search^h^h^h^h^h Sell-You-Things Engine.</p>
</blockquote>
<blockquote><p>If, when it really matters, the actual &amp; on-hand operating staff for $SYSTEM cannot understand even basics of the controls for $SYSTEM - then you're control UI is a complete failure, and you'll end up f*cked.</p>
</blockquote>
<blockquote><p>I have also started looking at it, by $REAL_JOB doesn't leave that much time.</p>
</blockquote>
<blockquote><p>Something I've always wondered is how people actually do the backwards alphabet test. I don't think I could efficiently do it sober, but I do have an O(n<sup>2</sup>) algorithm for doing it; sing the ABCs in your head, and only say the letter before the one you've already said.</p>
</blockquote>
<blockquote><p>So this means that /r/$FOO is not actually a subreddit about $FOO, moderated so that exactly any non-spam discussion about $FOO is allowed, but rather a dictatorship ran by whoever happened to grab the name first.</p>
<blockquote><p>if you don't like the actions of mods on a subreddit you can just make another subreddit (including in your company example, if the company does /r/$NEWPRODUCT and removes all negative opinions people can easily to /r/$NEWPRODUCT-community and run it as they wish instead)</p>
</blockquote>
</blockquote>
<blockquote><p>I was just thinking that this weekend in the Seattle area as we wandered the aisles for six for the wife. It's either an attempt to put the scariest-looking skull(s) on the label, or $SOMETHING $SEXUAL_REFERENCE $FARM_ANIMAL (e. g., Ass-Kickin' Horny Goat Stout).</p>
</blockquote>
<blockquote><p>Legitimately seems like a GPT-tier confusion. Just put all the somewhat-relevant words in a syntactically correct configuration and call it done.</p>
</blockquote>
<blockquote><p>Half a decade? Hasn't it been going on for well over 40 years by this point?</p>
<blockquote><p>s/decade/century/</p>
</blockquote>
</blockquote>
<blockquote><p>Please stop. These discussions never lead anywhere, and all they reveal in the end is that $SOME_AI_PROJECT doesn't meet $YOUR_IDIOSYNCRATIC_DEFINITION_OF_AI.</p>
</blockquote>
<blockquote><p>Time to enjoy some sweet O_2 molecules.</p>
</blockquote>
<blockquote><p>s/guy/woman</p>
</blockquote>
<blockquote><p>s/West/USA/</p>
</blockquote>
<blockquote><p>possible with the current deepf^Wparody technology</p>
</blockquote>
<blockquote><p>This Python dictionary could be useful for $HOBBYIST who is learning about $HOBBY and could help people familiar with $HOBBY to remember terms or use it for guidance in improving their skills at $HOBBY.</p>
</blockquote>
<blockquote><p>Newspapers do the kind of in depth investigation as their bread in butter that other mediums rarely if ever participate in.</p>
<blockquote><p>s/do/did/</p>
</blockquote>
</blockquote>
<blockquote><p>Oh dear $deity. Why does it insist on capitalizing "and" in the middle of a sentence?</p>
</blockquote>
<blockquote><p>Atheist as in "has replaced $god with some gnostic belief system", no thanks. </p>
</blockquote>
<blockquote><p>At $job-4 I had the following exchange with HR</p>
</blockquote>
<blockquote><p>The form this argument always takes is:</p>
<p>1) undesirable_outcome will not happen, and here's a rational argument why.</p>
<p>2) ok, undesirable_outcome did happen but it wasn't undesirable in the first place and here's a rational argument why</p>
</blockquote>
<blockquote><p>Adding a "if user lives in $city then he probably won't stay in a hotel in $city" would have a positive impact in all Google Maps users</p>
</blockquote>
<blockquote><p>Maybe they're a LLM and got confused by the keyword "conspiracy theory" and the double negative in the statement.</p>
</blockquote>
<blockquote><p>edit: s/meandering/rambling</p>
</blockquote>
<blockquote><p>Or just today I saw "$PROJECT is used by these awesome projects"</p>
</blockquote>
<blockquote><p>So complaints about rootkits^W anti-cheat is a banned topic now?</p>
</blockquote>
<blockquote><p>And he was really struck at first, like "Why would you write Hello World when we're building a $WHATEVER?"</p>
</blockquote>
<blockquote><p>Then he should be a $POSITION at $BIGCORP.</p>
</blockquote>
<blockquote><p>More realistically, you're more likely to hit "yes if there is compelling evidence that $choice is better than $status_quo with respect to $criteria".</p>
</blockquote>
<blockquote><p>&gt; It really cannot be overstated enough the wealth divide that pandemic QE created.</p>
<p>:s/\\&lt;created\&gt;.$/exacerbated./</p>
</blockquote>
<blockquote><p>At $oldjob, taking care of a busy and successful web estate that is now close to 25 years old, one of the ugliest and longest-standing warts was the "image store". That was a simple, flat directory on a single node, shared over NFS, which had accumulated more than 1.2 million (yes, 1_200_000) inodes/files in a single directory.</p>
</blockquote>
<blockquote><p>Also s/produce/release/g</p>
</blockquote>
<blockquote><p>Wow, now that's a case of paint^W checkering oneself into a corner.</p>
</blockquote>
<blockquote><p>s/popular/common/
I sometimes wish portrait oriented 4:3 monitors had become the dominant form.</p>
</blockquote>
<blockquote><p>I couldn't supply enough work for the contractors (we had to rush into it because ${life reasons}, so not everything was specced out when the work started)</p>
</blockquote>
<blockquote><p>IIUC this is what iMessage does (at least when Messages in iCloud or whatever it's called is disabled), except s/people/devices:</p>
</blockquote>
<blockquote><p>I work at $LARGE_CORPORATION and our product a11y team's most recent major ship was a new palette of colors with improved contrast ratios.</p>
</blockquote>
<blockquote><p>Welp, looks like that tweet got censored^Wdeleted.</p>
</blockquote>
<blockquote><p>s/tiktok/uber/g</p>
</blockquote>
<blockquote><p>The comments on the site have become predictable enough to be GPT generated.</p>
</blockquote>
<blockquote><p>My personal experience with devs who don't want to GPL their code or contribute to GPL projects is that they're under the impression that having a permissive license on their own project will get $corp to use it and, somehow, they'll profit from $corp using it.</p>
</blockquote>
<blockquote><p>s/Android/Windows/ and s/Europe/USA/ and it could be Microsoft at the end of the antitrust case in the 90s.</p>
</blockquote>
<blockquote><p>Large vehicles are safer for vehicle-on-vehicle or vehicle-on-object crashes, and vehicle-on-pedestrian crashes are a very small fraction of total fatal crashes. The core objection of this blog post^H^H^H^H^H^H^H^H paper is nonsense.</p>
</blockquote>
<blockquote><p>I love stories of little quirky &amp; fable-like people who are known as "that X person", and they seem to be a master at whatever X is. Creates a real charming atmosphere wherever these people exist.</p>
<blockquote><p>I imagine some of us are those kind of X persons in other peoples lives, where X ∈ {Computer, Science, Internet}.</p>
</blockquote>
</blockquote>
<blockquote><p>#upvotes !~ effort put into comment</p>
</blockquote>
<blockquote><p>Telling X and doing !X is not a recent invention.</p>
</blockquote>
<blockquote><p>Forgive me if I'm wrong but I suspect this comment is GPT3 generated. Regardless, it was hilariously absurd.</p>
</blockquote>
<blockquote><p>I'm sure this is good, like, really really good, but I'm struck by the feeling that it wants to baby talk me the same way Godel, Escher, Bach did.</p>
<p>I mean, it reads like a Homestuck pesterlog.</p>
<blockquote><p>Perhaps it's written by GPT-3..</p>
</blockquote>
</blockquote>
<blockquote><p>If I see someone is typing in a Slack channel, or typing to me privately, it takes all my focus away until it is complete.</p>
<blockquote><p>that means a person can DOS you by typing and erasing text but never sending you a message?</p>
</blockquote>
</blockquote>
<blockquote><p>Identifying this usage would require the compiler to be aware of the version control system, and still wouldn't correctly identify that the version sent from $COWORKER via email for some weird reason isn't a gradual change.</p>
</blockquote>
<blockquote><p>Is this a GPT-3 generated post? It reads like an answer to a completely different different question that just happens to hit the same keywords (e.g. "pay", "smart", "comfort", "social class"...)</p>
</blockquote>
<blockquote><p>I couldn't download .exe files at some $CORPORATION. They had to be whitelisted or something, and the download just wouldn't work otherwise.</p>
</blockquote>
<blockquote><p>I'm quite sure similar services can also be used by the average person. You simply google for these magical words '産廃業者 $CITY'.</p>
</blockquote>
<blockquote><p>&gt; It might sound silly but I'm always amazed at the fresh perspectives I'm able to obtain when I ask "someone smarter" for advice in my imagination.</p>
<blockquote><p>That sounds eerily like asking GPT3 to pretend to be someone else.</p>
</blockquote>
</blockquote>
<blockquote><p>But we're all living in the same system, and to put it in HN terms, we need to wipe and reinstall the OS.</p>
</blockquote>
<blockquote><p>The only switch type where actuation == tactile feedback that I know of is buckling-spring, as in the IBM model M.</p>
</blockquote>
<blockquote><p>I've always said it's not the Tree of Life, but rather the DAG of Life.</p>
</blockquote>
<blockquote><p>I still don't understand why this is a website thing and not an user-agent^W^W webbrowser thing.</p>
</blockquote>
<blockquote><p>My $DAYJOB task this week is beginning the process of splitting up a 14554 line C++ file.</p>
</blockquote>
<blockquote><p>At $prevjob we had the ability to do things like this at the push of a button, and generally would have within 90min of incident onset to help systems heal.</p>
</blockquote>
<blockquote><p>Correct. "Not doing $X anymore" doesn't mean you aren't an addict. "Being able to turn on/turn off doing $X" means you aren't an addict.</p>
</blockquote>
<blockquote><p>Note this is amending existing law, so think of it as a legal diff.</p>
</blockquote>
<blockquote><p>This native English speaker is with you, I thought RacquetNext was some new-fangled racquetball racket that was banned because it has carbon fiber plates that…do $SOMETHING, ala the Nike Vaporfly/Alphafly running shoes.</p>
</blockquote>
<blockquote><p>At this point, the onus is to prove thing $x is not used for Google analytics.</p>
</blockquote>
<blockquote><p>I don't think people do this because they want to be polite. The 'hello' is basically the start of what is the human equivalent of the TCP three way handshake.</p>
<p>- Hello (SYN)</p>
<p>&gt; Hi (ACK)</p>
<p>- How are you doing ? (SYN, ACK)</p>
<p>Basically, they are trying to set up a synchronous channel over what is essentially an asynchronous medium. This is even more annoying than just slowing down the answer, they are demanding your undivided attention during the conversation.</p>
<blockquote><p>You know you are on HN when hello/hi is explained in terms of SYN/ACK and not the other way around</p>
</blockquote>
</blockquote>
<blockquote><p>I tried their "Best toaster" query and one thing I want to say is just how hilarious it is that "best x"-of-anything webpage HAS to have $CURRENT_YEAR in the title. Yelp does something similar to its Google search results.</p>
<p>As if nothing good or the best in its breed ever came out from $PREVIOUS_YEAR or any previous years before that!</p>
<p>Tell me you're a garbage SEO website without telling me, just slap on "in $CURRENT_YEAR" or use some capital letters, I love how easy it is to tell the wheat from the chaff!</p>
<blockquote><p>Best $PRODUCT $CURRENT_YEAR can include products that were manufactured in $PREVIOUS_YEAR or $LONG_TIME_AGO. The $CURRENT_YEAR is saying that the list is up to date, not that all the items in the list were made in $CURRENT_YEAR.</p>
</blockquote>
</blockquote>
<blockquote><p>There is a joke I love that illustrates the concept:
"Ladies and gentlemen of the jury, my client was nowhere near the scene of the murder, he didn't mean to pull the trigger, and that son-of-a-bitch had it coming!"</p>
<blockquote><p>What would that be in programmer speak?</p>
<p>The intern had no write access to the production database, he didn't mean to do the commit, and our network engineers had it coming?</p>
</blockquote>
</blockquote>
<blockquote><p>A new version with s/Stalin/Putin/ would be good, and perhaps given oligarchs etc. even funnier.</p>
</blockquote>
<blockquote><p>When I'm tired and out of time instead of ordering an unhealthy meal from $FOODDELIVERYAPP$ I can (and very frequently do) buy something relatively healthier from such a site.</p>
</blockquote>
<blockquote><p>But, every $BIG_CORP out there has on their roadmap a long-term plan to either drastically reduce their spend on Oracle products, or more often, eliminate it entirely.</p>
</blockquote>
<blockquote><p>It's kind of terrible that you can use extensions XOR your own browser on iOS</p>
</blockquote>
<blockquote><p>I'm currently job hunting, and a bit tired of sending out 3 - 5 apps a day via indeed or $COMPANY_SITE. Actually talking to someone would be nice.</p>
</blockquote>
<blockquote><p>I immediately came home, tried logging into my online banking accounts, but couldn't due to 2FA. Of course, I won't be able to log in until a replacement phone arrives.</p>
<blockquote><p>It's the same failure mode if you s/phone/token/.</p>
</blockquote>
</blockquote>
<blockquote><p>There is nothing in any dishwasher, fridge, vacuum cleaner, washing machine, ${you_name_the_home_appliance} that would require to be smarter than they are without those (actually far from smart) hardware and software that is being installed and imposed on customers.</p>
</blockquote>
<blockquote><p>Typo in the headline: s/DNA/NDA/</p>
</blockquote>
<blockquote><p>I distro hop with my home laptops and use Windows at $JOB</p>
</blockquote>
<blockquote><p>&gt; If we assume that both groups visit government websites equally often</p>
<p>My pattern matching experience from real life tells me that this is unlikely....</p>
</blockquote>
<blockquote><p>The blog post misunderstand that many people that do that are simply using "hello" as a "ping" to see if someone's really there for immediate synchronous communication. They want to type just 1 word first ... and then wait for a human-style-TCP-ACKnowledgement</p>
</blockquote>
<blockquote><p>Some grouping or merging of URLs for major news would be nice, though hard to implement. For example Apple releases a new product, new version of Firefox, new S1 filing of $unicorn.</p>
</blockquote>
<blockquote><p>Yes, crows use poke, dig, find, free, head, tail, touch, more, mount, make. And if they don't like you, they use kill. Sometimes even killall.</p>
<p>crows are unable to use git, but then even I was unable to use git right after onboarding.</p>
</blockquote>
<blockquote><p>Unbounded extensibility is a great way to get adoption of a standard, in the sense that a lot of products will say "Fully $standard Compatible!" in their marketing materials. It is a bad way to ensure that the standard actually means anything outside the marketing materials.</p>
</blockquote>
<blockquote><p>Then again, I've used mailing lists since UUCP and VMS, so maybe I'm just exp^Wold.</p>
</blockquote>
<blockquote><p>English is the javascript of languages: full of warts and enforcing a status quo that sucks but everyone has to learn it because this is the world we live in.</p>
<p>Esperanto would be like Haskell: beautifully pure and regular but no one uses it except for a passionate community of die-hard zealots.</p>
<p>Romance languages would be like scripting languages (Python, Ruby, lua, etc.): once you know one the others get really easy.</p>
<p>Greek would be like Lisp: had a lot of influence on current languages, dwindled a bit, but is still around.</p>
<p>Biology is what happens when you try to decompile code written in assembler into OO language.</p>
</blockquote>
<blockquote><p>True statement. I lost a finger tip in a mandolin making potatoes lyonnaise. It mostly grew back. However, it has never felt the same. It's like the nerve endings in the finger tip throw unhandled exceptions.</p>
</blockquote>
<blockquote><p>A lot of therapists have a two year degree.</p>
<blockquote><p>What's your point?</p>
<blockquote><p>It's important to be careful about to whom you grant root access to your mind.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>For desktops HTTP2 is mostly ok, possibly an improvement. For Mobile it wasn't. I raised this when we were trailing in at $financial_media_company.</p>
</blockquote>
<blockquote><p>Normally I have to rely on reddit posts or random blogs that I find with search terms like "$gameType game with no P2P/P2W" or "$gameType game with no IAP" but even that's not perfect.</p>
</blockquote>
<blockquote><p>Sounds like Tamlin's 'assistant, a lawyer' is an id^H^H^H^H hasn't spent much time in the bush.</p>
</blockquote>
<blockquote><p>Reads like it was written by GPT-3.</p>
</blockquote>
<blockquote><p>At my $OLDJOB, I used ImageMagick to compare snapshots during some automated front-end testing on our public Drupal site.</p>
</blockquote>
<blockquote><p>I'm perfectly happy to use my brain as an index instead of a data warehouse. As in, I don't bother committing to memory things that I can reach out and grab if they fall out of my "cache".</p>
<blockquote><p>The problem for me now is my mental index can suffer from link rot.</p>
</blockquote>
</blockquote>
<blockquote><p>s/Browser/Chrome/, please.</p>
</blockquote>
<blockquote><p>Spaces in filenames were a mistake to begin with.</p>
<p>Spaces are used to separate parameters in the command line. There's also no real need for filenames to support spaces.</p>
</blockquote>
<blockquote><p>If I have some solution that can solve some major $PROBLEM, but a side effect of that solution is that some small group of people "get paid to do nothing", I'm completely OK with that.</p>
</blockquote>
<blockquote><p>Nginx is making the same mistakes that dethroned Apache. No one wants to fiddle with a web server. Default to $currentIndustryStandard, make it work, forward to my application.</p>
</blockquote>
<blockquote><p>Messaging apps are more like postal services — "please deliver this message to $person" — you're describing driving across town to drop something in a mailbox directly.</p>
</blockquote>
<blockquote><p>I suspect that you don't work at $BIG_CO and rather something more startup-y?</p>
</blockquote>
<blockquote><p>&gt; Whales have the capability to far outstrip humans in intelligence (if you accept that neuron count and neuron connections are the raw inputs).</p>
<p>Understood normally, the raw input also includes the software installed in the whale's brain. This limits the potential capability of whales.</p>
<p>If you were to overwrite that with software of your own design, you'd have a robot in the body of a whale, but not an actual whale.</p>
</blockquote>
<blockquote><p>Using it everyday too at $work, also used Slack and Gitter (and FreeNode/IRC FWIW).</p>
</blockquote>
<blockquote><p>Literally everything you wrote applies exactly the same if you s/E.164 number/email address/g and s/SMS/email/g</p>
</blockquote>
<blockquote><p>That's not really fair. You can want to have an "open enough" collaborative community without a trillion dollar company ($cloud_provider) steamrolling you and your work.</p>
</blockquote>
<blockquote><p>I was having to compete with professional spammers^H^H^H marketers for my friend's attention. It's gross.</p>
</blockquote>
<blockquote><p>We use that gateway email address with our notification alert system at $WORK.</p>
</blockquote>
<blockquote><p>Also, the page you were actually looking for is probably https://...</p>
<blockquote><p>I appreciate that you might think that, and thank you, but I was only interested in the 404 page.</p>
<blockquote><p>ML fails again. They need better training data</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>If you had to hazard a guess, what $EnterpriseVideoconferencingSuite would be most amenable to this sort of work?</p>
</blockquote>
<blockquote><p>&gt; Which is of course a fairly hard problem to solve - if you aren't having 2.2 children per couple then your population is shrinking.</p>
<p>You can increase throughput with parallelization or speedups of single threaded performance.</p>
</blockquote>
<blockquote><p>&gt; Anyone who really wants to stop a gaming addiction can stop it - it is only a question of will.</p>
<p>| sed "s/gaming/drug/"</p>
</blockquote>
<blockquote><p>Continuing to support $x means continuing to defend attack surface that's implemented as $y year-old code, to deliver a feature that in $z the majority of people do not use</p>
</blockquote>
<blockquote><p>Back when I worked at $HEALTHCARE_INUSRANCE_CO we were trying to solve this (more general problem) from the other side.</p>
</blockquote>
<blockquote><p>s/make it impossible/make it prohibitively expensive/</p>
</blockquote>
<blockquote><p>An interesting, short read is the difference between Emergency Use Authorization (EUA) and the full FDA approval.</p>
<p>Basically it is manufacturing, funding and red tape being the difference, nothing about safety or clinical trials differs.</p>
<p>In software terms, EUA is parallel tracked sprints for clinical trials and manufacturing at same time vs. regular approval is Waterfall where manufacturing occurs after approval.</p>
</blockquote>
<blockquote><p>s/Carlie/Charlie/, I assume?</p>
</blockquote>
<blockquote><p>If a NN is equivalent to $sophisticated_algorithm, that's a positive, not a negative.</p>
</blockquote>
<blockquote><p>In general he thinks a lot about tool kits and I think what we're seeing here is the math equivalent to being great at debugging.</p>
</blockquote>
<blockquote><p>Yep. I have a buddy who works at $BIG_US_RETAILER and he weekly has to go into the stock room with a box cutter and scissors to destroy unsold clothing.</p>
</blockquote>
<blockquote><p>Also, at $WORK we have three separate but related products, each with separate version numbers that we jumped at some point to reach the same value across all 3.</p>
</blockquote>
<blockquote><p>Ask HN: Which "old" programming language(s) you still use in your $DAYJOB?</p>
</blockquote>
<blockquote><p>That's a result of mindset and approach. Japanese can't focus on 2 or more problems, neither they can assess influence of one issue against another and see an ecosystem.</p>
<blockquote><p>Yeah sort of lacking the concept of hierarchy and/or recursion. "We need Z to do Y to do X" - no no the operand can't be a pointer to executable address. That'll fault.</p>
</blockquote>
</blockquote>
<blockquote><p>It shouldn't take horrific accidents for us to suddenly decide to care about structural safety.</p>
<blockquote><p>The problem is the same as it is in IT.</p>
</blockquote>
</blockquote>
<blockquote><p>a few years ago at $dayjob we had a fleet of CoreOS hosts.</p>
</blockquote>
<blockquote><p>While the topic is certainly interesting, the way that this article is written feels odd. It reads (to me) like GPT-generated text.</p>
</blockquote>
<blockquote><p>s/for JS/on NPM/</p>
</blockquote>
<blockquote><p>In the case above it wanted me to write out "That fly was sitting on something weird" - but I had written "That fly was sitting on something unusual" which is a perfectly valid interpretation of the sentence, but because they basically did a strcmp() I got it "wrong".</p>
</blockquote>
<blockquote><p>I've written on HN before about relatively simple things I have found useful (keeping a diary in $language, or watching films with $language audio)</p>
</blockquote>
<blockquote><p>Is this why I always get text messages from one of my friends that say &gt; Liked "{$ENTIRE_CONTENTS_OF_PREVIOUS_MESSAGE}"?</p>
</blockquote>
<blockquote><p>I remember when win2k was "the last good windows" and then windows xp after it. I $CURRENT-2 is always going to be considered "the last good windows".</p>
</blockquote>
<blockquote><p>Bureaucracy isn't that different from code. Some of it's dead legacy that needs to be removed, some of it's legacy that needs to be improved/replaced, some of it's legacy that's good enough, some of it's there for a damn good reason no matter how ugly it looks.</p>
<p>And when it comes to code, you probably trust devs who have specific insights into issues with code and promising replacements.</p>
</blockquote>
<blockquote><p>At $OLDJOB we used to use nfs extensively, and a common desire was to run some command on the system where the files actually resided</p>
<blockquote><p>By using $OLDJOB are you saying at every old job you ever held this was the case or just the last one? Because if it's just the last one you should have used</p>
<pre><code>const OLDJOB</code></pre>
<p>Not sure why you used a variable in a comment, maybe because this is hackernews and you want to resonate with other programmers?</p>
</blockquote>
</blockquote>
<blockquote><p>In my opinion, the code was very difficult to read and even harder to understand for !expert javascript devs and even for experts.</p>
</blockquote>
<blockquote><p>Is it just me or do "markets" feel like a really dumb AI that is incapable of any sort of strategic planning?</p>
</blockquote>
<blockquote><p>My email looks like ${firstname}@${some-german-word}.jetzt and I never had a problem with German speaking people.</p>
</blockquote>
<blockquote><p>For the "memorable phrase" thing, you may have already figured this out, but I find it's better to generate random passphrases for those. A string of words is a <em>lot</em> easier to say and verify.</p>
<blockquote><p>Yeah, he asked me for a new one as part of the reset (or just for his further amusement?!) - I just used my brain's PRNG for a few words for that.</p>
</blockquote>
</blockquote>
<blockquote><p>It would be tiring and a bit offensive to knowledgeable people, but maybe better then trying to debug the situation.</p>
</blockquote>
<blockquote><p>I have a scheme to give addresses per service, but I do this with gmail on my domain. It goes like $ACCOUNT+$RANDOMLETTERS@$DOMAIN.$TLD</p>
</blockquote>
<blockquote><p>If only. The amount of times people have tried to put mine down as $fistname.$lastname.$tld@gmail.com is genuinely sad.</p>
<p>did I ever say gmail.com &gt;&gt; no, but.. &gt; exactly, so let's try this again (goto 0)</p>
</blockquote>
<blockquote><p>"We have to recalculate weight on airplanes" is just a proxy, a code smell.</p>
</blockquote>
<blockquote><p>Hah. I regularly see patches whose review takes well over 100% the time it would have taken me to create the patch myself.</p>
<p>But then I'm employed by $MEGA_CORP and part of the job description is "helping to build and maintain the OSS community", so helping novices create their first patches is a big part of that.</p>
</blockquote>
<blockquote><p>You need so few subway drivers/conductors that does it really matter if you need one per train?</p>
<p>Taxis are probably average a driver per 1.5 ish customers. Subway/train is hundreds.</p>
<p>This reminds me of optimisation of non critical code. Sure its nice, but the cost/benefit is just not there.</p>
</blockquote>
<blockquote><p>I think that JavaScript and Python, from my experience, certainly increase speed... but that definitely != productivity.</p>
</blockquote>
<blockquote><p>But they could! It's as easy as saying "I can consult on $topic, please contact me to discuss details."</p>
</blockquote>
<blockquote><p>"Log in to see 8 answers on Qu^H^HStack Overflow!"</p>
</blockquote>
<blockquote><p>my bank has a section in their marketing email preferences where you need to check the box IF you don't want to receive any marketing^Hspam email.</p>
</blockquote>
<blockquote><p>If we chew on enough of these lozenges, will our teeth merge into one, curved megatooth?</p>
<blockquote><p>And why haven't we evolved to have that yet anyway?</p>
<blockquote><p>Well, in software terms, you're describing a monolithic architecture.</p>
<p>There is also the matter of how evolvable the platform would be. As a species, we're in the process of losing our wisdom teeth, and I suspect that would be harder to do if the skull/jaw/gum/tooth platform was... more tightly coupled, less modular.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Maybe selling a banana taped to a wall could be a way for people to make money, considering one of those sold for ${OUTRAGE}.</p>
</blockquote>
<blockquote><p>But americans do get marketing though. I bet you know about a bunch of companies that were doing $thing in $country before, probably with better outcomes, but never got the recognition.</p>
</blockquote>
<blockquote><p>On the other hand, $OTHER_COUNTRY has a history of $BAD_THING, so the only conscientious thing to do is boycott everything their workers produce too.</p>
</blockquote>
<blockquote><p>However, unlike software engineering, policy shouldn't be made to cover all the corner cases.</p>
</blockquote>
<blockquote><p>s/LTE stack/mmWave stack ?</p>
</blockquote>
<blockquote><p>Seems like the point? Add a bit of bias laund^H^H^H^H^H^H^H^H^H^Hmachine learning and you're good to go!</p>
</blockquote>
<blockquote><p>&gt; HN is a great community</p>
<p>HN feels like white collar GPT-3.</p>
</blockquote>
<blockquote><p>I've been watching a lot of bonsai YouTubers lately. The amazing thing about trees is how much abuse they will tolerate and still figure out some way to grow. You can find videos of somebody pruning all the leaf-bearing branches and half the roots off of a bonsai like it's nothing, fully expecting the tree to recover. Plants have a very flexible instruction set, something like a list of inner if-statements, which bonsai artists understand almost completely and use to dictate their growth. It seems like the default execution of this instruction set looks like "tree" when there are no particular challenges, but the plant would adapt to almost any trauma short of cutting it down to a 1ft high stump... and even then, you will find stumps growing branches.</p>
</blockquote>
<blockquote><p>The difference is between hobbyist agriculture and commercial agriculture. In the latter, consistency, pests and disease become more complex issues to deal with. IMHO there are a lot of analogies to software engineering. Small projects can engage in practices that don't scale, like skipping QA, not adequately testing, inconsistent coding standards.</p>
</blockquote>
<blockquote><p>Today in "GPT-3 ramblings or marketing spiel":</p>
<blockquote><p>that that kind of language long predated GPT-3. It learned from somewhere :)</p>
</blockquote>
<blockquote><p>I've noticed a recent tendency to compare people's writing to GPT-3 output as a way of saying something like "I didn't understand that" or "I found the style hard to follow".</p>
</blockquote>
</blockquote>
<blockquote><p>For some reason this reminds me of the website kuro5hin and how trolling was perfected into a sort of discussion-malware there. With Holocaust denial, it's discussion-malware with a highly targeted purpose: recreating the societal situation where another Holocaust can occur.</p>
<blockquote><p>&gt; discussion-malware</p>
<p>I've never heard this term and I like it. Relating software-of-mind to actual software can be a powerful intuition pump.</p>
</blockquote>
</blockquote>
<blockquote><p>I just hate that Sony can't be bothered to give an explicit send-off to the A-mount line. Coupled with their abysmal customer service reputation, this really makes Sony my textbook example of a faceless, uncaring $BIGCORP.</p>
</blockquote>
<blockquote><p>Sounds like just the next calculated step.</p>
<ol>
<li><p>Introduce $badThing with claims about page load performance.</p>
</li>
<li><p>tell sites they need to use $badThing to get into the "carousel"</p>
</li>
</ol>
</blockquote>
<blockquote><p>s/us/your phone, that we control the software for/</p>
</blockquote>
<blockquote><p>In fact, at $work, I developed a very similar architecture and it is running flawlessly for 15 months or so.</p>
</blockquote>
<blockquote><p>Earlier is better to expand something like this to $everything</p>
</blockquote>
<blockquote><p>If I had a mental s/Daily Mail/Reputable Newspaper/g I'd be able to give this article a little more time.</p>
</blockquote>
<blockquote><p>I have nothing nice to say about any Google-owned company but is this really all that different from $city's Best $business or literally any other corporate "award" that's nothing more than thinly-veiled advertising?</p>
</blockquote>
<blockquote><p>This is why you ALWAYS use a Privacy.com (or similar) virtual credit card with limited access to your bank account. It's the same as creating a separation between your API layer and DB layer.</p>
</blockquote>
<blockquote><p>A lot of people who I knew as sympathetic and calm before they took management roles turned into something I could code in one minute: namely a program that asks "how much is this going to take?" and if your answer is above N hours/days then they say "no, we're not doing it".</p>
</blockquote>
<blockquote><p>As in, you're valuable at $COMPANY_X, but you're not challenged to better yourself beyond what you already are. Much of your value to $COMPANY_X derives from skills and knowledge that are specific to $COMPANY_X / $INDUSTRY.</p>
</blockquote>
<blockquote><p>I've got the gift of the gab and so I see this differently. If I had no scruples or morals and chose to drive Uber, I could make an obscene hourly wage simply by picking up elderly people and engaging them in conversation. Most phone scams rely upon preying upon vulnerable, lonely people like this so sadly, it's a public 0 day with no known fix.</p>
</blockquote>
<blockquote><p>The money is there to drive things, pulling it out from one perspective is like looking at one weight in a neural network.</p>
</blockquote>
<blockquote><p>Was this article written with GPT3? It sure reads like it....</p>
</blockquote>
<blockquote><p>&gt; Wikipedia asks for money once a year</p>
<p>if once == "allYearLong" { clapYourHands() }</p>
<p>I don't think that I've opened a Wikipedia page in months, that didn't have a beg header.</p>
</blockquote>
<blockquote><p>I learned somewhere that certain people that pass a certain "threshold" are not only fooled^H^H^H^H^H^Hconvinced by Nigerian prince scams, but utterly convinced that they are true.</p>
</blockquote>
<blockquote><p>Constantly shiny + new ~== a good App Store.</p>
<p>(~== Not necessarily equal to. Did I just invent that?)</p>
<blockquote><p>Instead of inventing new operators in your sentences, you can also just stick with plain English</p>
</blockquote>
</blockquote>
<blockquote><p>As always, "Stop doing $negativebehavior" is less useful than the actual advice:</p>
</blockquote>
<blockquote><p>I can accept that they ran out of cash. It's just like a company running out of servers when traffic spiked.</p>
</blockquote>
<blockquote><p>Sears didn't just "go bankrupt" - it was killed by CEO Eddie Lampert.</p>
<blockquote><p>s/killed/looted</p>
</blockquote>
</blockquote>
<blockquote><p>There's such thing in Russian too. My parents had friends and they would come to our apartment every once in a while. We would call them all "uncle $name"</p>
</blockquote>
<blockquote><p>It's doubtful that any large bureaucracy can execute that decision quickly, though I do agree 3 years is way too long. I'd put 3 months as hard minimum even assuming every internal decision process went as smoothly as possible, and 6 months as a more realistic amount.</p>
<p>This largely has nothing to do with how grievous the transgression is or how sincerely the management wants to do the right thing. It has to do with red tape that is designed from first principles to permit zero exceptions or special cases that can cut through it quickly.</p>
<p>It's like choosing an O(n<sup>2)</sup> algorithm, baking it in as a load-bearing centerpiece, then being upset something doesn't happen in O(1).</p>
</blockquote>
<blockquote><p><a href="https://notabug.org/">https://notabug.org/</a> does eat its own Gogs fork^W^W dog food</p>
</blockquote>
<blockquote><p>AT&amp;T's home gateways have a maximum NAT translation table of 1024^H^H^H^H8192 connections.</p>
</blockquote>
<blockquote><p>Last month on HN someone got £7500 from FB, but, everyone thought he should have got more</p>
<blockquote><p>s/everyone/someone</p>
</blockquote>
</blockquote>
<blockquote><p>Note that there are 297 hidden items in that issue so you have to click "Load more..." ceil(297/60) times to read all of the comments</p>
</blockquote>
<blockquote><p>Ben Thompson tried to compare China with over 1.4 billion people with Taiwan with only 24 million people and vehemently arguing that the Taiwan's governance is better is very disingenuous at best. Imagine comparing the governance of large corporation vs. a small startup, it's just different.</p>
</blockquote>
<blockquote><p>I want you to know that any downvotes might have less to do with 2020 or identity politics and more so your sloppy style of conversation. Throwing out a wildly undifferentiated blanket statement and ending on a passive aggressive "cue the downvotes" is bad content in my opinion.</p>
<p>It enjoy being subjected to different views. I hope we can uphold a certain standard for expressing them around here.</p>
<blockquote><p>Your meta comment is also boring and unoriginal. GPT-3 itself could've generated it</p>
</blockquote>
</blockquote>
<blockquote><p>I read somewhere (on HN?) that Italians speak faster than Germans because their words contain less information, so in the end they still communicate the same amount of information per minute. Italians and Germans were used in the example because they were the two extremes.</p>
<blockquote><p>Is this RISC vs CISC human language philosophy?</p>
</blockquote>
</blockquote>
<blockquote><p>We refinanced during the pandemic with $MEGABANK, and they almost exclusively used third parties and email for the entire transaction.</p>
<p>I also called the office number I found at $MEGABANK's website to make sure they'd heard of me.</p>
<p>Beyond that, I had no reason to think $TOTALLY_LEGIT_ESCROW.com was not a phishing front.</p>
</blockquote>
<blockquote><p>At $WORK, we run a lot of 1000s of php servers with 10-20 processes each using nginx.</p>
</blockquote>
<blockquote><p>A 5-story building in China 'walks' to new location (cnn.com)</p>
<blockquote><p>City refactoring</p>
</blockquote>
</blockquote>
<blockquote><p>People intruding the private email? No problem. Politely refuse, and offer private consultancy at $very_high_price, with alternative, to open a bug in the bug tracker, making explicit that there is no commitment.</p>
</blockquote>
<blockquote><p>Watch and listen to pilots as they complete checklists. They point and callout each item, switch setting, etc.</p>
<blockquote><p>Came here for this.</p>
<p>A: "Passing control"</p>
<p>B: "Taking control"</p>
<p>A: "You have control"</p>
<p>B: "I have control"</p>
<p>This is how I remember it (6174, UH-1Y).</p>
<blockquote><p>The TCP handshake IRL</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Some reporter finds out that $government is doing $bad_thing. They pack up the evidence, rush out the door to talk to their editor. A slick, self-driving car pulls up as they exit the building, they enter and pull away.</p>
</blockquote>
<blockquote><p>&gt; personally never understood why one would pick this over date/hour</p>
<p>Providing a timestamp gives you the same information with higher precision <em>provided you know when now is</em>. I can tell you what day of the week it is, but not what the day of the month it is. Sometimes day of the week even takes extra cycles.</p>
</blockquote>
<blockquote><p>&gt; I am trying to be a good novelist, and hoping that people will forgive me for being a bad correspondent.</p>
<p>%s/novelist/coder</p>
<p>%s/a bad correspondent/unresponsive on slack</p>
</blockquote>
<blockquote><p>Unacademy focuses on test prep and thier anthem while sappy and gung ho, doesn't really have any references to gaming the system. <em>Crack it</em> is in the same vein as <em>Cracking the Coding Interview</em>.</p>
<blockquote><p>Which is gaming the system, if you ask me.</p>
<p>Cracking coding interviews would be considered "overfitting" in statistical learning parlance.</p>
</blockquote>
</blockquote>
<blockquote><p>I own a business. If I was to do a full inventory, I would probably find I'm in the possession of tens of thousands of items. I'm currently in the situation where something breaks daily. It has forced me into being very picky. I no longer buy things, if I can avoid it. Every thing I buy has the potential of being a problem down the line that will require time and attention. Even disposing of broken things can be a big hassle.</p>
<blockquote><p>This advice applies directly to software. Bring in more dependencies, bring in more custom tooling, and you're bringing in more maintenance burden. There will be bugs. Make sure you really need that shiny new framework.</p>
</blockquote>
</blockquote>
<blockquote><p>He wrote an OS project that runs all the infrastructure for your very own dictionary website.</p>
<blockquote><p>For the confused reader: s/OS/OSS/</p>
</blockquote>
</blockquote>
<blockquote><blockquote><p>Who else has an install base like Amazon in their Alexas and Rings?</p>
</blockquote>
<p>a company like Arris that sells DOCSIS3/3.1 cablemodems and GPON end user CPEs to big ISPs like Shaw, Comcast, Charter, Wave/RCN, etc. The main problem with that concept is that if a manufacturer of residential CPEs such as Arris made a unilateral decision to incorporate the tech into their cablemodems and other devices, their $BIGISP customers might not be pleased about it and would buy elsewhere.</p>
</blockquote>
<blockquote><p>Never attribute to malice that which is adequately explained by stupidity"</p>
<p>-Hanlon's razor</p>
<blockquote><p>Advanced malicious actors often feign incompetence when caught, to take advantage of the naive and forgiving.</p>
<blockquote><p>This is a basic part of the human OS.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Years ago for my sister's 30th birthday, I did a fun project involving the USPS.</p>
<p>I wanted to send her the message "Happy Belated Thirtieth Birthday!", which is 30 characters, via postcards, one character per postcard.</p>
<p>...</p>
<blockquote><p>You've reinvented TCP using paper packets.</p>
</blockquote>
</blockquote>
<blockquote><p>Because Alibaba, and now Amazon require tracking numbers from sellers.</p>
<p>From time to time they take these tracking numbers, and check them with APIs of shipping companies, and postal services. If a seller has too many invalid tracking numbers, or shipments that don't match the address, they rm -rf him.</p>
</blockquote>
<blockquote><p>If you are at $BigCorp there's probably only a rough correlation between what you are paid and what you are worth.</p>
</blockquote>
<blockquote><p>"Ping me" really threw me for a loop the first time I heard a non-tech say it in an obvious non-tech way.
It literally has no other meaning outside of the ICMP echo utility; and the onomatopoeia of hitting glass.</p>
<p>How did it enter the Business world?</p>
<blockquote><p>Ping comes from submarine sonar - not the networking world.</p>
</blockquote>
</blockquote>
<blockquote><p>Writing clearly is like playing Tetris. Sentences should be presented with clauses that drop down and slot together efficiently. At the earliest available opportunity you drop in a block that completes the line and points are won/made.</p>
<blockquote><p>Don't make your readers hold parts of the sentence in their head. Reorder or split sentences until it can be avoided. In other words, use a <em>really</em> small buffer.</p>
<blockquote><p>Overflowing that buffer really does feel like a stack overflow, too. Your whole mental state just suddenly disappears in a puff of smoke.</p>
<p>This resplendent sentence in the Vulkan spec did it to me the other day:</p>
<p>The layout of subresources of images on other logical devices that are bound to VkDeviceMemory objects associated with the same underlying memory resources as external memory objects on the lost device becomes VK_IMAGE_LAYOUT_UNDEFINED.</p>
<p>I got about half way through and suddenly discovered I didn't know where I was, what year it was or my name.</p>
<blockquote><p>That's a lot of qualified noun phrases. This should just be an s-expression. It's already one, honestly, but without the right parenthesis.</p>
<p>(becomes (qualified (layout of subresources of images on other logical devices) (bound . (same-memory-resources (VkDeviceMemory objects) (external memory objects on the lost device)))) VK_IMAGE_LAYOUT_UNDEFINED)</p>
</blockquote>
<blockquote><p>This is how I felt while reading philosophy essays. I'm not even sure half of those are even saying anything.</p>
<blockquote><p>A lot of philosophy essays are written as part of the writer being in the process of trying to figure things out for themselves, without quite having gotten there yet.</p>
<blockquote><p>It really shows, just like docs written by someone who hasn't actually understood what they're writing about yet.</p>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>I don't know if it's just me, but I very much prefer easy to skim emails to more dense ones.
At one point I got weekly project updates from a team I was working with, and one guy wrote dense, short, emails where I would have to read every sentence carefully to get a hang on what was going on. Another guy would write longer, fluffier emails but with bullet points and paragraphs in the same order:</p>
<p>Just by parsing the number of bulletpoints, and the length of each bullet point (and the first word) I would get a surprisingly good grasp on how things were going, and what was hard/complicated (longer bullet point -&gt; more complex), and very easy to read about exactly I wanted to know.</p>
<blockquote><p>This is how the so called sutras are written: a page of text gets compressed into one short sentence, so you have to stop after each of them and spend an hour unpacking its meaning, but the entire book is often just 200 sentences.</p>
<blockquote><p>.zip format in history.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Spotify CEO: musicians can no longer release music only "once every 3-4 years" (thefader.com)</p>
<blockquote><p>"What's he going to record a song about?"<br/>
"Nothing."<br/>
"Spotify'll kill him."<br/>
"I guess they will."<br/>
"He must have got mixed up in something with the music industry."<br/>
"I guess so," said Nick.<br/>
"It's a hell of a thing."<br/>
"It's an awful thing," Nick said.<br/>
They did not say anything. George reached down for his mobile phone and wiped the screen.<br/>
"I wonder what he did?" Nick said.<br/>
"Failed to write enough songs fast enough to generate user engagement. That's what Spotify will kill them for."<br/>
"I'm going to cancel my Spotify subscription," Nick said.<br/>
"Yes," said George. "That's a good thing to do."<br/>
"I can't stand to think about him waiting in the recording room and knowing he's going to get it. It's too damned awful."<br/>
"Well," said George, "you better not think about it."<br/>
(with apologies to Hemingway!)</p>
<blockquote><p>God help me, I can't tell if this is GPT-3 or not.</p>
<blockquote><p>"This writing looks computer-generated" as a compliment is kind of blowing my mind</p>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Roman numerals by and large were not used for calculating, but for recording calculations. Those calculations were done (as the very word "calculate" suggests) using pebbles or other tokens, on some kind of counting board.</p>
<p>The point of Roman numerals is to be as direct as possible a representation of the state of the counting board. They are a serialization format.</p>
<p>Think of it as the JSON (or s-expressions if you like) of the ancient world. You don't run your algorithm by writing and rewriting JSON literals over and over with pen and paper. They are just a record; your computation is done using a different automatic tool.</p>
</blockquote>
<blockquote><p>Many people assume their manager knows what the employee is doing. They often aren't, meaning they go by what they can see, which is lines of code. The smart thing to do is to regularly keep your manager updated on what you're doing, especially if they don't come by regularly and ask you.</p>
<blockquote><p>Rather than an integer number of lines of code, how about using a boolean : "it works" or "it does not work"?</p>
</blockquote>
</blockquote>
<blockquote><p>Not everyone has good managers.</p>
<p>$JOB-2, admittedly about 4 years ago now, the good manager with a background in software left for a better opportunity and was replaced by someone who's background was management.</p>
</blockquote>
<blockquote><p>My grandfather used to sit with me for an hour every morning and used to teach me maths.</p>
<p>He would focus on basics first. He would make sure I had the basics drilled in to me. Not just understood them, but mastered them. Then we would move on to the next topic.</p>
<p>It was a bit slow at first. But after a while, once the basics were done, I finished the whole year's math book in 2-3 months.</p>
<p>Drilling basics is basically like having the basics in O(1) look up with very reduced space complexity too. It reduces the amount of overhead your brain utilises.</p>
</blockquote>
<blockquote><p>This reminds me of a story I once heard about shipbuilding during WW2. Supposedly, although designs and blueprints were made in great detail, following them today wouldn't give working ships, because the workers implementing them at the time saw where the designs wouldn't work and fixed them of their own initiative.</p>
<p>In a way, the opposite of today's optimizing compilers.</p>
</blockquote>
<blockquote><p>I have a heuristic. A 'code smell' for social science articles,</p>
</blockquote>
<blockquote><p>Why do you think this is specifically a 'western society' thing? Browse Japanese sites and online tools and there is just as much gamification and 'thrilling' graphics and animation - if not more. Github looks like a technical white-paper in comparison.</p>
<blockquote><p>Modern Japanese culture is considered to be part of "western society", for better or worse.</p>
<blockquote><p>The converse statement is something like "Japan is not an eastern society" which just sounds... laughably incorrect.</p>
<blockquote><p>Of course it sounds ridiculous. Boolean algebra says the validity of a statement's converse is independent of the original. P → Q, tells us little about Q → P.</p>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>I love a good font (and try to use one on my own blog), but part of me gets really giddy when I discover a site with very little styling. It's like I've found some secret oasis that's going to have a high signal-to-noise ratio, or at least some more "raw" writing than you'd find on ${popularNewsWebsite}.</p>
</blockquote>
<blockquote><p>Remote work has its perks, until you want a promotion (wired.com)</p>
<blockquote><p>Humans don't have emotional object permanence.</p>
<blockquote><p>A perhaps more techie way of thinking of it is our relationships are in a mostly-LRU cache. The people we have interacted with most recently are the ones that matter most to us. If you aren't regularly resetting your place within that cache, you get bumped.</p>
<blockquote><p>The LRU cache analogy might be one of the best comments i have ever read.</p>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>We probably wouldn't hesitate to say "I don't believe $NONHUMAN_ANIMAL parents love their children, and vice-versa...." about most animals.</p>
</blockquote>
<blockquote><p>Blogging Is Not Dead (garron.blog)</p>
<blockquote><p>"X is alive / X is dead" is binary thinking and taking sides on this is pointless and divisive. Better to use a float rather than a boolean to model how popular something is.</p>
</blockquote>
</blockquote>
<blockquote><p>What surprised me most is that he presented himself as the biographer of James Bond, as if he were real. I wasn't aware of that history.</p>
<blockquote><p>It's a writer's technique somewhat similar to method actors who take on the character's persona as their own for the duration of a play or filming. Pretty much as far from "kill your darlings" as one can get, tbh.</p>
<blockquote><p>Forking your own thinking process to have a secondary personality execute in parallel sounds as close to "killing your darlings" as possible, given that personality would presumably have their own favorite expressions and tropes.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Interesting: whoever edited that article miscorrected "cobble" to the likely more familiar but incorrect "cobblestone".</p>
<p>I've seen a huge increase in such errors over the past decade, both in terms and tense. I don't know how to explain it but it is discouraging. Sometimes the errors are quite confusing until you realize what happened; this one is quite minor.</p>
<blockquote><p>In Minecraft "cobble" is certainly understood to be short for cobblestone. A "cobblegen" is either an arrangement of materials (lava, water, something heat resistant) so as to cause the game to make and replace cobblestone blocks when you mine them out, so as to obtain unlimited amounts of this building material, or in modded Minecraft it's a more compact machine which makes this material, perhaps in very large quantities indeed, and maybe related materials like sand.</p>
</blockquote>
</blockquote>
<blockquote><p>Hey, just a head's up, I was reading your blog and noticed a typo: s/formally/formerly/</p>
</blockquote>
<blockquote><p>eg. go watch a SGM play 30 second hyperbullet chess, I don't think I have either the memory or raw processing speed to do what they do, ever. If those guys exist a lot of other people have way higher ceilings. Also realize that chess (and by extension must be a lot of other activities) rely on both a crazy cpu, an amazing memory and an insanely built-out internal database of positions and ideas, and since my cpu is good but not unbelievable, my memory is nothing special and my time is limited there must be a lot of activities I'll never really be good at. Also just getting older and staying unsuccessful relative to your ego, you just start to compromise I think.</p>
</blockquote>
<blockquote><p>My response to picking up a number is to answer the call and say nothing. Auto-dial systems will route the call to a person when they get a "live" response. I don't know the criteria but I'm pretty sure it's them detecting noise on the call (which could be voicemail).</p>
<p>A human calling will wonder what is happening and fill the silence by saying something. A machine will not.</p>
<blockquote><p>I've encountered robots in the last 6 months that have started with "Hello?" when I didn't say anthing. It tripped up my Turing Test at first, but eventually hit an ALICEBot-like moment that crashed the whole thing down.</p>
</blockquote>
</blockquote>
<blockquote><p>Real wages haven't increased since 1970.</p>
<p>We only feel richer because:</p>
<p>* Most households have two wage earners now.</p>
<p>* We use lines of credit to borrow for discretionary purchases, we lease cars, pay by subscription for things more.</p>
<p>* We have more gadgets. (Note that this has been proven to make people any happier.)</p>
<blockquote><p>&gt; Note that this has been proven to make people any happier.</p>
<p>s/Note/Not/? Or are you missing a "not" somewhere else?</p>
</blockquote>
</blockquote>
<blockquote><p>In my former life as a consultant I came to very much dislike personas as implemented by ux designers.</p>
<p>Personas are a magnet for prejudice: they are based on the idea that clusters of people all behave the same (old people, young people, professionals, stay-at-home mothers, what have you).</p>
<blockquote><p>Yes, the very idea that customers can be bucketed is flawed. We are all unique people.</p>
<blockquote><p>I think GP is fine with bucketing, but they're saying that f(age, gender, interests, ...) is not a good hashing function.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Is it possible that people simply continue to think about what they were trying to learn, consciously or not, when given no tasks? Why is the control comparing people who tried to learn information and then took a break to people who tried to learn even more information? Would a more insightful control be to look at how people who took a break compared to people who continued studying the material during the break time?</p>
<blockquote><p>I agree with you entirely. I want to make a few points though. First is that even when I am actively listening to someone speaking, my brain will make associations with the new information (coming from my auditory sensory registers), so I context-switch involuntarily every second. Think of process preemption.</p>
<p>Roughly like this:</p>
<pre><code>t0: hear the word "dog";
    my auditory registers record it

t1: my brain processes the word "dog";
    I imagine what "dog" looks like
    hear the word "cat" concurrently, goes into registers

t2: my brain processes the word "cat"
    I imagine what "cat" looks like
    hear the word "lamb" concurrently, goes into registers</code></pre>
</blockquote>
</blockquote>
<blockquote><p>I've timed it in actual play: it's quicker to google for a monster stat block (one from the SRD, obviously) than it is to look it up in the Monster Manual sitting right next to the laptop.</p>
<blockquote><p>Sure, but from experience in play, sticky notes in the Monster Manual are much faster for switching back and forth than browser tabs are.</p>
<blockquote><p>That's a cache, though, when the use case at hand is random access.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Brain is very active when you sleep, a lot is happening. Imagine sleep is just a mutex on big set of data on which computation needs to be made, compression, data reorganization, index optimizing. Data that you received that day is segregated , decisions are made which data to keep and which to delete. After that lock is freed and you wake up. If you don't sleep your system will run slower and slower over time and will timeout more and more queries until it will start to return corrupted data. You need to sleep to keep the system healthy.</p>
</blockquote>
<blockquote><p>tl;dr: It's not because the cardboard boxes are significantly better than cribs and other sleeping methods, it's the fact that the box comes with almost everything a new parent needs to look after kids, which means they don't have to shell out $foo-money to be able to take care of the children properly, and aren't left worried that they left something out.</p>
</blockquote>
<blockquote><p>English pronunciation &lt;-&gt; writing is so far from a mapping, it causes a lot of resource waste.</p>
<p>Even if there are dialects that pronounce the same word differently, you could still find a lot of common ground.</p>
<blockquote><p>Finding that common ground would mean switching from our current system of somewhat-arbitrary spelling to a different but very similar system of mostly-arbitrary spelling. It imposes the same memorization burden on everyone and the benefit is slightly more predictable pronunciation within each of a set of officially-blessed dialects. That ground gets lost over time regardless; there is a reason predictable pronunciation is a feature of spelling systems that either (1) are new, or (2) have just undergone reform.</p>
<p>If your biggest problem lies in a circumstance you rarely encounter, arguably fixing it is not a priority.</p>
<p>&gt; for example I always mess up the words study and student, it's infuriating</p>
<p>This is a funny example to use, since it fully conforms to the rules I described above — study uses the STRUT vowel, and student uses the GOOSE vowel. It would be a better example for the complaint that we have more sounds than symbols.</p>
<blockquote><p>Good points, but I disagree. Decoding and encoding becomes a lot harder, if vowels change depending on consonants coming after them, or something else even further down the line. (I'm not a linguist.)</p>
<p>It's like a config file specification that supports gotos.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote><p>Because there's a bit of a feedback loop with success. If others see you as being successful or impressive (on whatever axis they care about), this attracts them to you, and having a more admirers increases your social power and influence, which gives you more opportunities and better chances of success in your future efforts.</p>
<p>This also explains [redacted]'s formula for career success, which is basically:</p>
<p>10 Make something awesome</p>
<p>20 Tell people about it</p>
<p>30 GOTO 10</p>
</blockquote>
<blockquote><p>English suffers from the same problem as UNIX: they're both good enough.</p>
</blockquote>
<blockquote><p>Or you know, just look away from the monitor, in the physical space of your office/whatever instead of a flat surface, which would have similar benefits.</p>
<blockquote><p>So many office spaces are rectilinear and planar. I find the fractal real world to be so much more restful to look at. It refreshes my mind in a way unlike any manufactured surface.</p>
</blockquote>
</blockquote>
<blockquote><p>There are few things in life more important than choosing one's peer group well. The Internet gives you many more options than we had available prior to the existence of it. Choose wisely and re-evaluate that choice periodically to see whether your peer group continues to represent your goals and values.</p>
<p>Why? Your peer group literally gets arbitrary code execution on your brain. (It's a flaw in MonkeyBrainOS 1.01 which we haven't patched yet.)</p>
</blockquote>
<blockquote><p>It's common amoung US companies as well. How many tech companies aren't available in !USA? How much "streaming TV shows" aren't available in !USA?</p>
</blockquote>
<blockquote><p>Walking Through a Doorway Makes You Forget (2011) (scientificamerican.com)</p>
<blockquote><p>Not entirely unlike running a garbage collection cycle (or freeing a pool) after each HTTP request...</p>
</blockquote>
</blockquote>
<blockquote><p>Great read. Somewhat of an off-topic question, but here goes:</p>
<p>&gt; Watkins wanted her pen name to be spelled in lowercase to shift the attention from her identity to her ideas.</p>
<p>I'm sure what I'm about to say has been discussed before — but wouldn't this accomplish the opposite effect? I feel her "unconventional" name makes me focus more on it, not less (especially when her name is used at the beginning of a sentence, where we are trained to expect a capital letter no matter what). Do other people with lowercase names have similar justifications?</p>
<blockquote><p>To me it's a small pattern breaking annoyance, like a linter warning I can't turn off that triggers every time I see it.</p>
</blockquote>
</blockquote>
<blockquote><p>Mobile app stores handle a lot of hassle like distribution and billing. I would avoid complex backend at all cost if you don't want to be on 24/7 devops duty. Maybe periodically updated CVS at basic webhosting would be enough.</p>
<blockquote><p>s/CVS/CSV/</p>
</blockquote>
</blockquote>
<blockquote><p>Slight note about Dan Wang picking 2005: That was the peak of CS degrees awarded because it's 4/5 years after the height of the dot-com bubble. So the upward bump in the mid-2000's is somewhat explainable as an anomaly.</p>
<blockquote><p>Re: the choice of 2005 as origin, if you rebase on 2009, then CS degree growth looks on par with other STEM fields.</p>
</blockquote>
</blockquote>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/cyborgs_on_hn/cyborgs_on_hn.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/c376eeb2e0b2e7411e20788a3e749211a103445f"><time datetime="2024-03-03T12:00:42-08:00">2024-03-03</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/42d498f3f711d060c12ee609507220239d1388ba"><time datetime="2023-09-12T19:42:38-07:00">2023-09-12</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/071b6d102fe2138da036d9319c4556de5d6b0008"><time datetime="2023-08-30T21:30:10-07:00">2023-08-30</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/62e49edfd0bc973277950491ee07a36e7f84c3d8"><time datetime="2023-07-20T21:40:24-07:00">2023-07-20</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/76dabe771be790e429e4d599366d5fc06cd5328a"><time datetime="2023-02-23T18:45:15-08:00">2023-02-23</time> Add a few more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/76fa58959f43b28a14dd283f716938c0c32743af"><time datetime="2023-01-01T14:11:15-08:00">2023-01-01</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/201d2a32217c5e2f54c96576c6e6fd3fdbfbbef1"><time datetime="2022-06-04T15:04:12-07:00">2022-06-04</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/94609bc2b48ebd73d6e352dfae5cb44309a2f4ff"><time datetime="2022-04-03T02:53:34-07:00">2022-04-03</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/5edc6469973cf993dd8f459cfe855af53aef1bcd"><time datetime="2022-03-14T15:10:15-07:00">2022-03-14</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/faad6f8fb47a2c26329941185cf4000707fc7bb3"><time datetime="2022-02-05T17:02:13-08:00">2022-02-05</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/48788bfc3b1865a879d596ad26395827074fce51"><time datetime="2022-01-07T22:45:05-08:00">2022-01-07</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/66ee353cdbd16de10eb65373beb27984086dda15"><time datetime="2021-11-07T19:40:39-08:00">2021-11-07</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/70be9e946e4a695aba79e1e318a6f475c8a4844a"><time datetime="2021-08-10T17:29:27-07:00">2021-08-10</time> Add more comments, replace smart quotes.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/a67c9d18f431f437d2a8d5165ade1aa83744fc8e"><time datetime="2021-06-28T18:38:43-07:00">2021-06-28</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/eabe0fa5ad84f0cb35fd2df62b2d48a732fd8dad"><time datetime="2021-06-10T19:10:10-07:00">2021-06-10</time> Replace caret html entities with properly escaped carets.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/63324ca8246f9d4c9f55057e3a20a89c6f091ce7"><time datetime="2021-06-07T19:10:00-07:00">2021-06-07</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/e044aad1474479a7acadde46d3e6dd6a2196f0a5"><time datetime="2021-05-17T17:04:21-07:00">2021-05-17</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/2118592fa0d136c69fbdef9fd046daea457d625c"><time datetime="2021-03-05T16:07:34-08:00">2021-03-05</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/ad0408655dbcae8c4995da820bfce06ac0edf507"><time datetime="2020-12-02T00:06:13-08:00">2020-12-02</time> Add five more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/0a49eb09cdba9523e21b34cf11ee91aa31365ce9"><time datetime="2020-10-27T01:47:10-07:00">2020-10-27</time> Add more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/dc98f09f451152bd435a19e4815f863e3e5fbc94"><time datetime="2020-09-19T19:37:23-07:00">2020-09-19</time> Add Hanlon's razor Human OS comment.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/6d8c2284cae5e8bc519157249f71f8a69349e859"><time datetime="2020-09-15T15:06:53-07:00">2020-09-15</time> Add many more comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/385c02e8684929c9a8e101b8036cc38b94b423cd"><time datetime="2020-08-31T21:30:15-07:00">2020-08-31</time> Add managers comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/20773fd90b0b25018adee055c218ea28d3926afc"><time datetime="2020-08-31T21:30:06-07:00">2020-08-31</time> Triumphant return of the hyphen break.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/1dd66812d2c1cfabe3c2d6d6e7fa54cec76242be"><time datetime="2020-08-18T22:55:24-07:00">2020-08-18</time> Add wwii shipbuilding, grandpa math O(1) comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/1f7bb9953543525549c5ae36f8ce79f61e5871d7"><time datetime="2020-08-10T21:29:19-07:00">2020-08-10</time> Add code smell aka heuristic comment.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/a4b8744fc067ad2b8853cc74173dbc9bf0f53469"><time datetime="2020-08-06T14:33:35-07:00">2020-08-06</time> Add popularNewsWebsite, cultural boolean algebra comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/f4a5ce72426f734f8f940d0ae7cb1b115cbbcc9a"><time datetime="2020-07-29T11:55:36-07:00">2020-07-29</time> Add nonhuman animal, social LRU comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/f67be4670dae029994003c31ae6c6a53b6e3df44"><time datetime="2020-07-21T23:35:01-07:00">2020-07-21</time> Add blogs dead boolean float comment.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/c7d33c7ff3b787be2ad3ebfb6889718cdd8dbf4a"><time datetime="2020-06-28T18:07:03-07:00">2020-06-28</time> Replace single hyphen visual gaps with empty <p>.</p></a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/7f3dc646eee56c3c4525815d1680b418cf913126"><time datetime="2020-06-28T18:05:23-07:00">2020-06-28</time> Add blog typo, minecraft cobble, james bond comments.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/d840d4a56f4f53a2ad56a4f66587c235997fa402"><time datetime="2020-05-13T17:14:45-07:00">2020-05-13</time> Add chess comment.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/d13e711731f2645a9bb103635862a074f2716d02"><time datetime="2020-04-02T20:22:05-07:00">2020-04-02</time> Add turing test comment.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/c9ec853d892b45aa30d87897a2c6123ccfff44a0"><time datetime="2020-03-27T23:24:21-07:00">2020-03-27</time> Add cyborgs_on_hn.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 7b46f8468479f9271f5c04bafc43c45af3b7c38c/motivations_for_writing
title: My motivations for writing
url: https://voussoir.net/writing/motivations_for_writing
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="meta" href="/writing/tags/meta">[meta]</a> <a class="tag_link" data-qualname="introspection" href="/writing/tags/introspection">[introspection]</a></p>
<h1>My motivations for writing</h1><ol id="table_of_contents">Table of contents<li><a href="#introduction">Introduction</a></li><li><a href="#creativity_and_visibility">Creativity and visibility</a></li><li><a href="#my_other_art_and_future_ideas">My other art and future ideas</a></li><li><a href="#influences">Influences</a></li></ol>
<h2 id="introduction">Introduction<a class="header_anchor_link" href="#introduction"> (§)</a></h2>
<p>I have always hated writing. You've heard people say "I'm so bad at math", but I have said "I'm so bad at writing" just as many times. If my English teachers saw that I had begun writing voluntarily I think some of them would be shocked, and some of them would say I had the capacity but never the drive.</p>
<p>My grammar in these articles is probably not great. In one English class my professor pointed out that I make comma splices on almost every page. I have an affinity for turning things into bullet pointed or numbered lists which in somes cases is the best presentation for the material but in other cases is a cop-out to avoid prose. I have no problem writing sentence fragments. Or overusing em-dashes and parentheses. And I start sentences with conjunctions. For some reason I have a fascination with stringing together very long hyphenated phrases in an I-wish-I-had-an-adjective-to-use-here-but-I-guess-I'll-have-to-make-my-own kind of way. But the fact that this page you're reading even exists is a testament to the fact that I have decided to prioritize expressing myself and reaching people rather than writing "well" or following any particular structure.</p>
<h2 id="creativity_and_visibility">Creativity and visibility<a class="header_anchor_link" href="#creativity_and_visibility"> (§)</a></h2>
<p>Generally I think that programming can be an art form. The choices of naming, indentation, docstring formatting, quantity and quality of comments, and API design are all areas rife with opinionation and the ability to impress. Or at least the ability to enrage when they are bad. Code can last a long time and have wide-reaching impact. I am proud of the code that I write, and I write it with the expectation that it will be read, not just run. Experimental ideas get the <code># WARNING: Bad code ahead!</code> treatment but for the most part I think I do a good job.</p>
<p>The problem is I can't share the joy of my work with anyone who isn't also a programmer, which is most people. They won't understand the work that goes into it, the decisions I made and how they compare with the alternatives, the extensibility and modularity of my design, the consistency of my whitespace, or how excellently the comments explain the rationale behind some particular particularity. I can spend several hours putting an application together, and no matter how useful it is to me <a class="footnote_link" data-index="1" href="#footnote_text_1" id="footnote_link_1" title="[1] This section is basically me whining about not getting enough praise so let me clarify. I write programs because I enjoy it and because they benefit me. Here's a bunch of scripts unlikely to be read by anyone, but which represent the me-shaped indentation I've left on my computer like a bed that you slept on too long. The more things I automate, the more time I have left over to... search for something else to automate. It's just that, let's face it, sometimes I wish I could show code to people and have them say &quot;wow, nice!&quot;.">[1]</a>, the most I'll get out of friends or family is "oh, cool". It's worse for backend code, where there's no visible product whatsoever besides the code itself. Not even other programmers will be interested in reading through <em>that</em> unless they expect to find something novel or some accidentally-published credentials to borrow.</p>
<p>What did you do today? Um, well, I have a program that helps me organize ebooks and it had a problem so I spent about three hours fixing it. Oh, cool.</p>
<p>I'm not totally detached — I know that musicians, authors, filmmakers, actors, dancers, visual artists, architects, and engineers of all trades experience this exact same feeling. I know that cutting room floors are messy the world over. I've seen <a href="https://en.wikipedia.org/wiki/Misery_(film)">Misery</a> so I know the blood, sweat, and tears that go into writing a book. But for most of these, you have an end product which people understand, recognize, contemplate, compliment, love, hate, collect, burn, sing along to, cry over, are frightened by, have revelations through, make videos about, wear, get tattoos of, and decorate their homes with. The only time programs really wow non-programmers is when they output <a href="https://old.reddit.com/r/generative/">art or music</a> and in that case it's not the program they care about. I sympathize with security professionals, technical support, and janitors whose job it is to remain invisible.</p>
<p>I have likened programming to art, but really it is more like craftsmanship. If a craftsman makes a beautiful hammer, other craftsmen can appreciate it but most people will just want to hit things with it... which is its purpose, after all, but if only they would enjoy it a little more?</p>
<p>This is why I have decided to start writing these articles. I have many ideas and though I am absolutely fraught with <a href="/writing/counter_counter_thoughts">counter-counter-thoughts</a>, I'm doing it anyway so that I can feel like I've actually used the thoughts for something instead of letting them be forgotten in timid silence. I want to have something to show non-programmers for what I do and what I think. To explain my interest in such-and-such technology in some way other than "here's a git repository I made for that".</p>
<p><a class="footnote_text" data-index="1" href="#footnote_link_1" id="footnote_text_1">[1]</a> This section is basically me whining about not getting enough praise so let me clarify. I write programs because I enjoy it and because they benefit me. <a href="https://git.voussoir.net/voussoir/cmd">Here's a bunch of scripts</a> unlikely to be read by anyone, but which represent the me-shaped indentation I've left on my computer like a bed that you slept on too long. The more things I automate, the more time I have left over to... search for something else to automate. It's just that, let's face it, sometimes I wish I could show code to people and have them say "wow, nice!".</p>
<h2 id="my_other_art_and_future_ideas">My other art and future ideas<a class="header_anchor_link" href="#my_other_art_and_future_ideas"> (§)</a></h2>
<p>This isn't to say that I've done nothing but programming and am just now trying something else for a change.</p>
<p><strong>Drawing</strong>: I'm not a super great artist but I have always enjoyed doodling. I have a digitizer tablet which I mostly use as a mouse-substitute but I enjoy doing digital sketching, and I like the additional handwritten flair when I need to annotate screenshots instead of using MS Paint text.</p>
<p><strong>Youtube</strong>: I was 15/16 when Minecraft lets-plays became huge on the internet. My computer at the time didn't get very good framerates, so although I tried recording a few lets-play style videos I knew it wasn't really worthy of publishing. Instead I made some tutorials about redstone and worldedit because subpar framerates are tolerable if the lesson is good. As I got older I was not happy with how my voice sounded in those videos, and did not want people finding them, so I deleted them both from youtube and my computer.</p>
<p>I later did some tutorials for writing reddit bots with PRAW, but when PRAW moved from v3 to v4, those videos became obsolete and I decided to unlist them.</p>
<p>The only valuable video left on my channel is <a href="https://www.youtube.com/watch?v=tZCy0Ew1I0g">2<sup>2n</sup> - 1 = 3k</a> and I would like to do more in this vein. I've always enjoyed kinetic typography videos (<a href="https://www.youtube.com/watch?v=rNxoLJy3m3s" title="Network - Mad as Hell">one</a>, <a href="https://www.youtube.com/watch?v=J7E-aoXLZGY" title="Stephen fry - Language">two</a>, <a href="https://www.youtube.com/watch?v=c6Q0dfrbr10" title="V for Vendetta - V Monologue">three</a>, <a href="https://www.youtube.com/watch?v=8Gv0H-vPoDc" title="Weird Al - Word Crimes">four</a>, <a href="https://www.youtube.com/watch?v=KCSA7kKNu2Y" title="Weird Al - Stop Forwarding That Crap to Me">five</a>, <a href="https://www.newgrounds.com/portal/view/558516" title="Ricepirate - Dot Dot Dot">six</a>) and educational math channels. <a href="https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw/videos">3B1B</a> was already big by this time.</p>
<p>Many of the articles I write here are things that I would like to make videos about. But while text is cheap and gittable, videos are not, so I think this is the better way to start.</p>
<p><strong>Minecraft otherwise</strong>: My first real programming experience beyond .bat files was in Java, which I had to learn in order to make mods for Minecraft. I made my own ores, tools, plants, and trees. About 30% of the items I made had some semblance of legitimate purpose, the rest were because I had come up with some pixel art that I liked and wanted to make an item for it. If you want to see some python projects that spawned out of my Minecraft interest, see <a href="https://git.voussoir.net/voussoir/else/tree/master/VoxelSphereGenerator">VoxelSphereGenerator</a> and <a href="https://git.voussoir.net/voussoir/else/tree/master/Minecraft3DVector">Minecraft3DVector</a>.</p>
<p><strong>TF2</strong>: I have 1700 hours in TF2. Some hundreds of those are from idling to get refined metal back when it was 2.33 per key. Out of the active play hours, I'd say at least 90%+ were spent on cp_orange_x3 or variant maps. There was a server with a 100% crits mod that I played on regularly, and when I made my own orange map cp_orange_Skyward they were kind enough to host it and I became a moderator on the server. Here are some screenshots of Skyward (<a href="/writing/motivations_for_writing/tf2_skyward1.jpg">one</a>, <a href="/writing/motivations_for_writing/tf2_skyward2.jpg">two</a>, <a href="/writing/motivations_for_writing/tf2_skyward3.jpg">three</a>, <a href="/writing/motivations_for_writing/tf2_skyward4.jpg">four</a>) and I will see about cleaning up the messy vmf and uploading that.</p>
<p>Making texture replacement skins for TF2 is pretty easy, but for a bigger challenge I tried Blender to make a hat. I only got so far (<a href="/writing/motivations_for_writing/tf2_hat1.jpg">one</a>, <a href="/writing/motivations_for_writing/tf2_hat2.jpg">two</a>) before giving up, but at least I can say I've used Blender.</p>
<p>Future ideas:</p>
<p>A little more than a year ago I discovered the world of fan-made film commentaries. The first one I saw was <a href="https://drive.google.com/file/d/11KK4tKOtzTHbADB8DIq3eATGWQ1cVxRz/view">David Taylor's Birdman commentary</a>, then I discovered <a href="http://www.tysto.com/">Tysto</a> and <a href="http://www.popcornpoops.com/">popcornpoops</a> and a few others. I think this is an awesome idea and I've wanted to make some ever since, but I haven't gotten around to it yet. You have to find enough interesting things to talk about to make it worthwhile.</p>
<h2 id="influences">Influences<a class="header_anchor_link" href="#influences"> (§)</a></h2>
<p><a href="http://paulgraham.com/articles.html">Paul Graham's articles</a>, particularly <a href="http://paulgraham.com/nerds.html">Why Nerds are Unpopular</a> and <a href="http://paulgraham.com/essay.html">The Age of the Essay</a>. Thank you Paul for helping me realize that writing can be approachable, and that the 5-paragraph assignments from school have wrongly commandeered the word essay.</p>
<p><a href="https://en.wikipedia.org/wiki/David_Foster_Wallace">David Foster Wallace</a>. I learned about DFW via Will Schoder's video <a href="https://www.youtube.com/watch?v=2doZROwdte4">The Problem with Irony</a>. Then I read DFW's <em>E unibus pluram</em> (1993) and <em>Brief Interviews with Hideous Men</em> (2000) and watched <a href="https://www.youtube.com/watch?v=iGLzWdT7vGc">this interview he did on German tv</a>. I have not read any of his other works yet but I will get to them in time.</p>
<p>I don't read books very often. The number of books I've read on my own accord is very small. So maybe I'll sound like a noob discovering the <a href="https://www.youtube.com/watch?v=DgzACnYsV8U" title="Spongebob - Squidward finds canned bread">best thing since sliced bread</a>. Having said that, <em>Brief Interviews</em> is one of the few things I've ever read where I was actually amazed at what I was seeing. I say "seeing" and not "reading" because at the moment I'm not even talking about the message, but rather the formatting, the torrent of footnotes and asides, the introduction of terms like [flexion of upraised fingers to signify tone quotes] which over the course of a few pages shortens to [finger flexion] then [f.f.], the unabashed reuse of chapter titles and recurring segments, and the chapter <em>Datum Centurio</em> which is written like an excerpt from a dictionary from 2096 with an obscenely overwrought system of superscripts, daggers, subsections, IPA, and index cross-references (KEY at BABE, CYBER-). Suddenly my poor use of the comma doesn't seem so bad. Reading this book is like watching <a href="https://en.wikipedia.org/wiki/2001:_A_Space_Odyssey_(film)">2001</a>'s stargate sequence or <a href="https://en.wikipedia.org/wiki/Beyond_the_Black_Rainbow">Beyond the Black Rainbow</a> or <a href="https://en.wikipedia.org/wiki/House_(1977_film)">House</a> or <a href="https://en.wikipedia.org/wiki/Let_the_Corpses_Tan">Let the Corpses Tan</a> to some extent. I have always had a tendency to overuse parenthetical asides, but my growing affection for footnotes is thanks to DFW.</p>
<p>I hear that James Joyce is similar and will likewise get around to reading his work.</p>
<p>Similarly, <a href="https://www.gwern.net/index">gwern.net</a>. The funny thing is I don't actually read gwern, I've read maybe one or two articles in total. I consider it an influence because he also goes very heavy on the markup formatting and asides (in fact, between the icons indicating the domain of every link, and the asides which physically crowd the main article, I'd say it's a bit too much for me), and I particularly like that there's almost always code snippets, graphs &amp; charts, even embedded audio clips in some cases. I would like to add more rich media to my writing too, but because this website is saved as a git repository, I'm perpetually hyper-aware of the <em>don't store binaries in git!</em> fairy reminding me that the repo size is going to bloat if I do that. Maybe I should put the files on s3 and just link them here? But then the site becomes more fragile and loses its cohesion as a cloneable, offlineable, full website? We'll see what happens. Hmm but I'm linking to third parties too so maybe I need to be archiving those links <a href="https://www.gwern.net/Archiving-URLs">like gwern does</a>.</p>
<p>I also like that Gwern articles invariably link to multiple other Gwern articles, in a twisted web not unlike the brain itself which spawned it.</p>
<p><a href="http://danluu.com/">danluu.com</a> for keeping the most minimal plaintext blog I've ever seen. So much so that I have to use Stylus to give the page a column width or else it's cinemascope levels of head turning to read.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/motivations_for_writing/motivations_for_writing.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/3475cb0abf63172e6392487ffaf41755f71a18e3"><time datetime="2023-09-18T21:01:09-07:00">2023-09-18</time> Replace github links with git.voussoir.net.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/0ea84744e4cd853ad0a7ef2ef57e9226672eb37a"><time datetime="2022-10-06T19:09:13-07:00">2022-10-06</time> Replace DFW German interview link with a working copy.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/9748c34baa7d8f5c27ac43bca6879ae16c64435f"><time datetime="2022-04-07T11:34:39-07:00">2022-04-07</time> Link directly to the Birdman commentary file.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/9cce9f9ef055a5e268a9b913871ff8d0473cf3eb"><time datetime="2021-06-07T19:08:19-07:00">2021-06-07</time> Move S3 images to my domain.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/90f8cdbba1de17da017fa2f9e3e536bc260f614c"><time datetime="2020-09-21T02:05:53-07:00">2020-09-21</time> Upload tf2 screenshots to s3.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/770cad146b3e2bf98d0697f1c17192af3e5792cd"><time datetime="2020-06-25T13:56:54-07:00">2020-06-25</time> Add tag introspection.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/de54130987f437513fb4d1fc915a5766e9fdbe28"><time datetime="2020-03-29T22:48:20-07:00">2020-03-29</time> Add tag meta.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/90e5af39121c478af764caa681bec547ec68f498"><time datetime="2020-03-29T22:45:40-07:00">2020-03-29</time> Fix typo "bunch scripts" -&gt; "bunch of scripts".</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/7b46f8468479f9271f5c04bafc43c45af3b7c38c"><time datetime="2020-03-28T19:48:28-07:00">2020-03-28</time> Add motivations_for_writing.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 234c4431c79127d733aaf3dc395a71c502d206e1/lying_git_history
title: Lying git history
url: https://voussoir.net/writing/lying_git_history
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="git" href="/writing/tags/git">[git]</a> <a class="tag_link" data-qualname="meta" href="/writing/tags/meta">[meta]</a> <a class="tag_link" data-qualname="today_i_did_this" href="/writing/tags/today_i_did_this">[today_i_did_this]</a></p>
<h1>Lying git history</h1><ol id="table_of_contents">Table of contents<li><a href="#introduction">Introduction</a></li><li><a href="#backup">Backup</a></li><li><a href="#windows_users_use_git_bash">Windows users, use git bash</a></li><li><a href="#filterbranch_to_modify_paths">filter-branch to modify paths</a></li><li><a href="#rebase_i_to_edit_existing_links">rebase -i to edit existing links</a></li><li><a href="#rebase_again_to_reset_commit_dates">rebase again to reset commit dates</a></li></ol>
<h2 id="introduction">Introduction<a class="header_anchor_link" href="#introduction"> (§)</a></h2>
<p>When I started writing these articles (you know, 2 weeks ago), I decided to put them into subdirectories for broad categories: <code>python</code>, <code>life</code>, and <code>computers</code> are what I had so far. For example, <code>/writing/life/friction</code>.</p>
<p>Because this site is statically generated from the directory tree, moving files and folders around would change their URL, so once I put an article into a category it would be stuck there unless I was willing to break the link. The reasoning for making category folders was:</p>
<ul>
<li>Keeps the main /writing directory cleaner.</li>
<li>If I write an article about Python's <code>continue</code> statement as <code>python/continue</code>, I could later create <code>java/continue</code> if need be. I wouldn't have to cautiously prefix every article like <code>python_continue</code>.</li>
</ul>
<p>Today, however, I decided that the cons outweighed the pros because:</p>
<ul>
<li>I already have a tag system, with which I can add and remove tags freely <a class="footnote_link" data-index="1" href="#footnote_text_1" id="footnote_link_1" title="[1] I do think category systems can have a place alongside tags. Will expand on this in the future.">[1]</a>.</li>
<li>The chance of me reusing names like that is low. For life articles, I could amend the original or come up with a new name; for programming articles, why would I need anything other than Python?</li>
<li>If I make categories too broad and want to split them, I won't do so for fear of breaking links.</li>
<li>If I make categories too narrow and only have a single article in them, I may want to lump it into a bigger one but won't do so for fear of breaking links.</li>
<li>I don't want to waste time with nginx rewrite rules or generating duplicates of the output files so that articles can be read with and without the category in the url.</li>
</ul>
<p>So I decided to move everything out of the category dirs. Since this site is new and nobody has any links to it yet, it's better to break them now and get it over with. But because git history and commit timestamps are central to my publishing model, I needed to keep that intact or else lose all this soon-to-be-nostalgic early history.</p>
<p>There are plenty of tutorials on the internet for modifying git history. This is just a short description of what I did today in case I needed to do it again sometime so I'll have a reference.</p>
<p><a class="footnote_text" data-index="1" href="#footnote_link_1" id="footnote_text_1">[1]</a> I do think category systems can have a place alongside tags. Will expand on this in the future.</p>
<h2 id="backup">Backup<a class="header_anchor_link" href="#backup"> (§)</a></h2>
<p>I zipped up the repository so even if I mess everything up I could extract it back and start over.</p>
<h2 id="windows_users_use_git_bash">Windows users, use git bash<a class="header_anchor_link" href="#windows_users_use_git_bash"> (§)</a></h2>
<p>The commands used in the following steps will use <code>sed</code> and linux style environment variables and linux style single quotes around some commands, so Windows users should run <code>bash.exe</code> which is located alongside <code>git.exe</code> in your git installation folder.</p>
<h2 id="filterbranch_to_modify_paths">filter-branch to modify paths<a class="header_anchor_link" href="#filterbranch_to_modify_paths"> (§)</a></h2>
<p>The original paths were like <code>/writing/life/friction/friction.md</code> and I needed to turn them into <code>/writing/friction/friction.md</code>. Just needed to replace /life/, /python/, and /computers/ with /.</p>
<p>I found <a href="https://stackoverflow.com/a/3063008">this stackoverflow answer</a> which quotes a premade snippet from the docs:</p>
<blockquote><p>To move the whole tree into a subdirectory, or remove it from there:</p>
<pre><code>git filter-branch --index-filter \
        'git ls-files -s | sed "s-\t\"*-&amp;newsubdir/-" |
                GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                        git update-index --index-info &amp;&amp;
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD</code></pre>
</blockquote>
<p>The SO comments mention that using hyphen as the sed separator is flaky so I went ahead and used <code>#</code> instead without even trying <code>-</code>. I assume the escaped <code>\"</code> is for filepaths with spaces, but it didn't help in my case so I removed it. For me, the command was was:</p>
<pre><code>git filter-branch --index-filter \
        'git ls-files -s | sed "s#/life/#/#" | sed "s#/python/#/#" | sed "s#/computers/#/#" |
                GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                        git update-index --index-info &amp;&amp;
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD</code></pre>
<p><strong>Since filter-branch is dangerous, make sure to test out the <code>git ls-files -s | sed</code> commands separately first.</strong></p>
<p>However this was raising an error during the final <code>mv</code> step because the index.new file referenced by the variable didn't exist. I'm not sure if it was supposed to exist already since the snippet from the docs doesn't mention it. I found <a href="https://stackoverflow.com/a/46677910">this SO answer</a> saying to just add <code>; /bin/true</code> to just ignore that, so the final error value is 0 and git doesn't abort.</p>
<pre><code>git filter-branch --index-filter \
        'git ls-files -s | sed "s#/life/#/#" | sed "s#/python/#/#" | sed "s#/computers/#/#" |
                GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                        git update-index --index-info &amp;&amp;
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"; /bin/true' HEAD</code></pre>
<p>This worked and now all the paths were correct. But the articles themselves contained links that I needed to fix.</p>
<h2 id="rebase_i_to_edit_existing_links">rebase -i to edit existing links<a class="header_anchor_link" href="#rebase_i_to_edit_existing_links"> (§)</a></h2>
<p>I used <code>git rebase -i commithash</code> where commithash was just before the earliest case of an article that contained a link. I set commits to <code>e</code> if they involved an article that had a link.</p>
<p>Rebase <code>e</code> performed the commits in question — adding or editing the article — and then paused the rebase so that I could edit the link in the article to just <code>/writing/friction</code>, then do a <code>git commit --amend</code> so the change became part of the commit as if it was correct all along.</p>
<p>This left authorship dates intact but set commit dates to be the current time.</p>
<h2 id="rebase_again_to_reset_commit_dates">rebase again to reset commit dates<a class="header_anchor_link" href="#rebase_again_to_reset_commit_dates"> (§)</a></h2>
<p>Rebase has a flag called <code>--committer-date-is-author-date</code> to copy the author date to the commit date, which is what I needed. For some reason this cannot be used with <code>-i</code> simultaneously, but simply running a new rebase with this flag and no other changes fixed it up.</p>
<pre><code>git rebase --committer-date-is-author-date commithash</code></pre>
<p>Then to publish,</p>
<p>On my side:</p>
<pre><code>git push origin master --force</code></pre>
<p>and on the server side:</p>
<pre><code>git fetch --all
git reset --hard origin/master</code></pre>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/lying_git_history/lying_git_history.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/147587b253cdfcd32a36d67df260934ceaa51a1b"><time datetime="2020-09-20T21:27:33-07:00">2020-09-20</time> Add tag today_i_did_this.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/234c4431c79127d733aaf3dc395a71c502d206e1"><time datetime="2020-03-29T22:48:38-07:00">2020-03-29</time> Add lying_git_history.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: c7c581ad7a21478f224a1001f7cd76ad0e456a81/complicated_topics_insurance
title: Complicated topics: Insurance
url: https://voussoir.net/writing/complicated_topics_insurance
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="life" href="/writing/tags/life">[life]</a></p>
<h1>Complicated topics: Insurance</h1><ol id="table_of_contents">Table of contents<li><a href="#introduction">Introduction</a></li><li><a href="#health_insurance_preexisting_conditions">Health insurance &amp; pre-existing conditions</a></li></ol>
<h2 id="introduction">Introduction<a class="header_anchor_link" href="#introduction"> (§)</a></h2>
<p>When it comes to forming opinions, nobody can sit down and critically analyze every opinion and belief that they hold, myself included of course. And that's understandable, because there are just so many opinionable topics in our lives that we simply have to prioritize what to think about and leave the rest to instinct, upbringing, and whim. Otherwise we'd be doing nothing <em>but</em> opinionating.</p>
<p>The phrase "standing on the shoulders of giants" gets thrown around a lot, to the point that I'm pretty desensitized to it now. After all, it's true for just about every aspect of everything, and it sounds poetic, so it gets repeated. But occasionally I will sit down and try to think critically about a topic I want to form an opinion on only to get lost, tangled, and overwhelmed in the details.</p>
<p>It's one thing to stand on the giants, it's another to look down.</p>
<p>These articles are dedicated to those topics. Topics where the underlying, primordial elements sound reasonable but the current, evolved state of affairs is objectionable, and for the life of me I can't find the line in between. Topics where I'm supposed to pick the best course of action to help the most people but every path seems wrong for one reason or another.</p>
<h2 id="health_insurance_preexisting_conditions">Health insurance &amp; pre-existing conditions<a class="header_anchor_link" href="#health_insurance_preexisting_conditions"> (§)</a></h2>
<p>A small group of people get together and say "Hey, hospital visits are usually more expensive than I can afford at any given time, but they're very rare. If we all agree to share some money in a pool, then we'll each be putting money in and each be getting benefits out, so it comes out fair and makes us all safer". And lo, private health insurance is born.</p>
<p>I know what you're thinking: the government to which these people pay taxes should already be serving this purpose so they wouldn't feel financially endangered in the first place. But hold that thought because that's where the lost, tangled, and overwhelmed part is going to come in. Anyway, that's what the situation <em>ought</em> to be and not what it is. On its face, there's nothing so wrong with the actions of these people: they saw a problem in their lives and banded together to solve it. Surely a small group like this wouldn't think they could influence the government to enact socialized healthcare, and if they did try to go that route they would be unprotected in the meantime. Foreshadowing alert: there's nothing more permanent than a temporary solution.</p>
<p>For this nascent health insurance collective to operate, they'll have to calculate some statistics regarding the frequency of hospital visits and the cost of those visits so they can divide it up and have everyone pay a bill each month into the central pool. If the collective is being lead ethically, it won't be seeking excessive profit or hoarding money for its own sake. That can be a big <em>if</em> but this is a theoretical piece.</p>
<p>At some point, someone new comes along who wants to join the collective. This person has some kind of health condition and needs frequent, expensive treatment. The statistics on which the other members' payments were decided weren't calibrated for this. The collective will have two choices: provide this person with the same level of coverage and premium as everyone else, even though it won't be enough for their higher bills; or increase <em>everyone's</em> premium so that this person receives the same level of care, proportionally.</p>
<p>It does seem unfair to treat the newcomer differently than anybody who's already in the group. Life is unpredictable, and it's possible that anyone in the collective can develop this kind of expensive condition at some point. But, if they've got a contract with their insurance that prevents their premium from increasing, then that's part of the financial risk that the insurance collective accepted when they formed and calculated the rates. Now we're talking about someone who has a pre-existing condition <a class="footnote_link" data-index="1" href="#footnote_text_1" id="footnote_link_1" title="[1] A note about this vocabulary. There is a movement around &quot;Being a woman is not a pre-existing condition&quot;, and other &quot;____ is not a pre-existing condition&quot; sentiments. The word condition here really poisons the tone because in common speech it almost always means sickness or illness or something wrong with you. If you re-interpret pre-existing conditions from the point of view of the insurer as meaning &quot;there is some fact about you which is visible from the outset and changes our expectations about the amount of coverage you're going to cost us&quot;, then it's very easy to conclude that their statisticians have, for whatever reason, found that people who are or who have ____ cost them more coverage than their median member. Does that mean I'm happy to see people get charged higher premiums for something that they can't change about themselves? Of course not. This is where we get into distributing costs among the wide demographic, or letting the narrow demographic pay different rates closer to their expenditures. After thinking about this for as long as I have, I can't help but see these people as being angry at statistics, though the vocabulary doesn't help either. They might do better to redirect their anger towards the government for not making a national health insurance, since the private sector is where these demographic-specific statistics take effect. If the entire population is under a single insurance, there are no demographic lines any more. Under private health insurance, understanding what your rates should be is nigh impossible with the information gap between regular people and the insurers who invest billions into collecting stats on their target demographic. Under national health insurance, your rates derive from national expenditure divided by population size.">[1]</a>, which gives the insurance the luxury of deciding whether to accept or deny their application.</p>
<p>What should they do? Nobody wants that person to be in pain (or worse, die), but if this is a private group who have all agreed to share their private money, they're well within their rights to decline the new member. Nobody can force you to accept someone into your club, right? I hesitate to write this because it sounds so bad, but it's true. Everybody would have to pay more without getting any additional benefits because the new member would be taking out a disproportionate amount, which will certainly lead to sour feelings (if they're sour enough, they could splinter off and form a new group with the original rates) even if it's for the greater good of humanity by helping this new member. It's not easy to convince people to give up more of their money for the good of humanity <a class="footnote_link" data-index="2" href="#footnote_text_2" id="footnote_link_2" title="[2] So, why do I keep saying that tax-funded healthcare is okay? Isn't that exactly &quot;giving up more money for the good of humanity&quot;? This is an area where the tangibility, the line-of-sight to the cost, has a big psychological impact. Even though we can look at the government's spending of our taxes and deduce that X% of my Y dollars was spent on roads, and X% was spent on schools, I for one don't really feel those percentages. Taxes are more like a black box where money goes in and results come out. Case in point: if taxes were paid as itemized bills, and every citizen in the US received a separate &quot;Bombs to Drop on Countries that Have Oil we Want&quot; bill in the mail, they'd be shocked and outraged. But taxes aren't itemized like that, and the fact of the matter is we're all paying this Bombs tax already, but few think about it and even fewer complain. For the rest, it's invisible. If healthcare were migrated into taxes, I do assume it would wind up being cheaper, but at the very least it would be invisible like the Bombs tax and people would stop complaining about it so much.">[2]</a>.</p>
<p>The only entity that can <em>force</em> the insurance collective to accept the new member would be the government. Gov could say "let everyone in without discrimination or else your collective is illegal" and poof, it's play along or disband (or be outlaws. Healthy outlaws). The likelihood of government taking notice of the collective grows as the collective itself grows and more members start to depend on it. This is especially true if the collective grows so big as to overtake competing insurers, such that the marginalized groups of people with pre-existing conditions have even fewer choices of insurance collectives to join.</p>
<p>Competing insurers? What? Why do they need to compete? In the primordial state, the collective is made up of people whose full-time job is something else. There's probably a leader who had the idea to form the collective in the first place, and a person who's good with stats to calculate the bills. In this state there's not much reason to 'compete' with other groups doing the same thing. That's like competing with the adjacent neighborhood to see who can share more sugar. But as more people join, someone's going to have to step up and become a full-time leader or treasurer or statistician to manage the collective and its money pool. As soon as that happens, and they've quit their day job, their livelihood relies on the insurance collective in more ways than one, and now the group definitely needs an income stream greater than their outbound stream, and <em>that</em> means they need to compete.</p>
<p>If insurers want to compete by offering low rates, they'll want to exclude people with pre-existing conditions. Insurers that accept expensive people will charge expensive premiums, and only expensive people will be joining them.</p>
<p>You know I don't normally cite things in these articles but this passage says exactly I'm thinking:</p>
<blockquote><p>In the absence of regulatory interventions in a Private Health Insurance market, insurers might tend to adopt practices that seek to minimise their risk to avoid losses, including denial of coverage for applicants who have preexisting health conditions (Kofman 2006). On the other hand, overregulation might exert enormous stress on insurers, resulting in strangulation of the market (a situation whereby insurance schemes are unable to function in a sustainable manner and therefore are forced to shut down) (Sekhri 2005b).</p>
</blockquote>
<p><a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4441071/pdf/CD011512-0001.pdf">Government regulation of private health insurance (2015)</a> [pdf]</p>
<p>The idea of government regulating the practices of private insurance, to me, sounds like a joke. But not the funny kind, I mean the dumb kind. Firstly, because if the government wants everybody to have an equal opportunity at receiving healthcare, then... why is that not part of the taxes? And secondly because it forces every insurance collective to converge toward a single point, where every insurer is nothing more than a new face and logo on top of a singular system known as government-approved operating practices and non-discriminatory membership. Even in the primordial state, insurance collectives have very little room for flavor — put money in, get money out, the amounts of which are based on the stats of the collective's demographic. The only flavor is the demographic. So the reckless skateboarder whose membership application into the Stay At Home Knitters Health Insurance collective was rejected will instead have to stay in the Reckless Skateboarders collective, with appropriately higher rates which correspond to the cost of living that lifestyle. An insurer that casts a wide demographic net will, by definition, see the cheaper members subsidizing the more expensive members, while a narrow demographic net will charge rates that more accurately reflect the costs of the member, which may be quite high if you live a dangerous lifestyle.</p>
<p>Notice that if the entire collective consists of people making frequent withdrawals, whether they're reckless skateboarders or people with expensive medical conditions, then the whole concept of amortizing costs falls apart as members are making deposits and just as quickly turning them around for withdrawal. The premiums at that point must become as expensive as the bills they cover, and everyone is essentially paying their own bills indirectly through the insurance. Cheap members <em>must</em> subsidize expensive members in order for the concept of the insurance's cost amortization to work.</p>
<p>Unlike the reckless skateboarders, people with expensive pre-existing conditions didn't choose that lifestyle. Under a self-segregating and self-pricing private system, there is logically no collective who would enthusiastically accept the pre-existing condition people, who introduce a high cost through no fault of their own. Realistically, the <em>only way</em> to give these people reliable support (which they deserve, as we all do) within our society is for it to be mandated by government, thus washing away the only variable that could distinguish the collectives in the first place.</p>
<p>Oh, and that's not to mention the practical arms race that occurs between hospitals and insurers, where the price of everything inflates so comically high that non-insured people don't stand a chance, certainly not those with expensive conditions. Once insurance becomes commonplace, hospitals can raise their prices through the ceiling because who cares haha the insurance will pay for it. And the individuals, because they're paying indirectly through their insurance premiums, and only encounter the hospital under bad circumstances, have little leverage.</p>
<p>I know that there are people who oppose government-run healthcare because it brings the word "socialism" to mind. They'll certainly feel embarrassed when they realize that the entire concept of any insurance at all is based on the socialized distribution of risk and funds. Sure, government health insurance is socialistic and private health insurance is not-socialistic, if you need to draw a strict political distinguishing line. But practically, it's difficult to say the current free market of health insurance really does what free markets are thought to do. 49% of Americans get their health insurance <a href="https://www.kff.org/other/state-indicator/total-population/?selectedDistributions=employer">from their employer</a>, so they're stuck with their employer's choice of insurance provider and plan unless they want to go it alone. The other 51% are, I suppose, self-funding their insurance, or covered under government Medicare, or simply don't have any. It's only some portion of that 51% that's exercising their ability to shop around for insurances (which, again, have few legitimate ways of distinguishing themselves), but it's 100% of us that get caught in the pricing arms race and in-network hospital requirements; and it's the people with pre-existing conditions who deal with the anxiety of only maybe being covered.</p>
<p>REMEMBER: there's nothing more permanent than a temporary solution. It would be great we if could go back in time, to the moment the first private health insurance collective was formed, and say Hey no, start a movement for government-run tax-funded healthcare instead, or else we're going to wind up with a bunch of vaguely unique health insurance companies acting as proxies for the government's wishes under tight regulation except taking a privatized slice off the top of your bill every month <a class="footnote_link" data-index="3" href="#footnote_text_3" id="footnote_link_3" title="[3] Why do I sound so sure that a government-run insurance would be cheaper than private insurance? The government is not known for its economic efficiency. Firstly, private insurance companies have costs that a government insurance wouldn't have, such as advertising. Here are a couple of numbers about that (one, two). Secondly, and probably more impactful, I expect that if the government was the one paying out for coverages, they wouldn't give in to the pricing arms race game that hospitals want to play. Another technique the industry uses to reduce individual power is to create partnerships between insurers and hospitals, and to establish &quot;service areas&quot;, within which you should go to an insurer-sanctioned hospital or else they won't cover the visit. Much of this wouldn't be possible when there's only a single insurer in the nation.">[3]</a>. But now every aspect of this system is so tightly intertwined, especially with regards to that pricing arms race, that it's easier for the government to regulate private insurers into acting like government bodies than to actually fix healthcare as a government body.</p>
<p>BUT, that's not what's on the ballots. You know what's on the ballots? "Should insurers be required to accept pre-existing conditions [_] Yes [_] No". Oh my god. If I say yes I'm allowing the government to get away with yet again lazily slapping more restrictions on private insurers instead of saddling up and creating a tax-based healthcare which is obviously what this is supposed to be, contributing to the decay I've described above, and encroaching on the rights of private people in private groups to do with their private money what they wish. And if I say no, people will die.</p>
<p><a class="footnote_text" data-index="1" href="#footnote_link_1" id="footnote_text_1">[1]</a> A note about this vocabulary. There is a movement around "Being a woman is not a pre-existing condition", and other "____ is not a pre-existing condition" sentiments. The word <em>condition</em> here really poisons the tone because in common speech it almost always means sickness or illness or something wrong with you. If you re-interpret pre-existing conditions from the point of view of the insurer as meaning "there is some fact about you which is visible from the outset and changes our expectations about the amount of coverage you're going to cost us", then it's very easy to conclude that their statisticians have, for whatever reason, found that people who are or who have ____ cost them more coverage than their median member. Does that mean I'm happy to see people get charged higher premiums for something that they can't change about themselves? Of course not. This is where we get into distributing costs among the wide demographic, or letting the narrow demographic pay different rates closer to their expenditures. After thinking about this for as long as I have, I can't help but see these people as being angry at statistics, though the vocabulary doesn't help either. They might do better to redirect their anger towards the government for not making a national health insurance, since the private sector is where these demographic-specific statistics take effect. If the entire population is under a single insurance, there are no demographic lines any more. Under private health insurance, understanding what your rates <em>should</em> be is nigh impossible with the information gap between regular people and the insurers who invest billions into collecting stats on their target demographic. Under national health insurance, your rates derive from national expenditure divided by population size.</p>
<p><a class="footnote_text" data-index="2" href="#footnote_link_2" id="footnote_text_2">[2]</a> So, why do I keep saying that tax-funded healthcare is okay? Isn't that exactly "giving up more money for the good of humanity"? This is an area where the tangibility, the line-of-sight to the cost, has a big psychological impact. Even though we can look at the government's spending of our taxes and deduce that X% of my Y dollars was spent on roads, and X% was spent on schools, I for one don't really <em>feel</em> those percentages. Taxes are more like a black box where money goes in and results come out. Case in point: if taxes were paid as itemized bills, and every citizen in the US received a separate "Bombs to Drop on Countries that Have Oil we Want" bill in the mail, they'd be shocked and outraged. But taxes aren't itemized like that, and the fact of the matter is we're all paying this Bombs tax already, but few think about it and even fewer complain. For the rest, it's invisible. If healthcare were migrated into taxes, I do assume it would wind up being cheaper, but at the very least it would be invisible like the Bombs tax and people would stop complaining about it so much.</p>
<p><a class="footnote_text" data-index="3" href="#footnote_link_3" id="footnote_text_3">[3]</a> Why do I sound so sure that a government-run insurance would be cheaper than private insurance? The government is not known for its <a href="/writing/complicated_topics_insurance/nber_use_it_lose_it.html" title="National Bureau of Economic Research (2014) -- Federal agencies spend 4.9 more in the last week of the fiscal year in order to use up the rest of their allocated budget, lest they be allocated less for the next year">economic efficiency</a>. Firstly, private insurance companies have costs that a government insurance wouldn't have, such as advertising. Here are a couple of numbers about that (<a href="/writing/complicated_topics_insurance/contently_insurance_advertising.html">one</a>, <a href="/writing/complicated_topics_insurance/iamagazine_insurance_advertising.html">two</a>). Secondly, and probably more impactful, I expect that if the government was the one paying out for coverages, they wouldn't give in to the pricing arms race game that hospitals want to play. Another technique the industry uses to reduce individual power is to create partnerships between insurers and hospitals, and to establish "service areas", within which you should go to an insurer-sanctioned hospital or else they won't cover the visit. Much of this wouldn't be possible when there's only a single insurer in the nation.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/complicated_topics_insurance/complicated_topics_insurance.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/50d6b5b71e08633f52c7aaac7292feaf6a464e1d"><time datetime="2022-04-07T12:40:54-07:00">2022-04-07</time> Move archived pages to my own folder.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/1486c482b617619de918ecf079f40c48c9e10701"><time datetime="2022-01-07T22:45:34-08:00">2022-01-07</time> Add tag life to several articles.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/6928d655d3a882e76a28bb211baad8494b112c92"><time datetime="2020-08-18T22:57:59-07:00">2020-08-18</time> Rename complicated_topics -&gt; complicated_topics_insurance.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/99ffba6369b84cc3df0f9650a45c601c15f0651f"><time datetime="2020-07-11T16:36:47-07:00">2020-07-11</time> Add paragraph about socialism.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/c7c581ad7a21478f224a1001f7cd76ad0e456a81"><time datetime="2020-05-19T22:12:44-07:00">2020-05-19</time> Add Complicated topics - Insurance.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 47b98020821918d6b4442f6e59a3f6e776bc231d/inkscape_batch_png_pdf
title: Batch png to pdf with Inkscape & PDFtk
url: https://voussoir.net/writing/inkscape_batch_png_pdf
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="today_i_did_this" href="/writing/tags/today_i_did_this">[today_i_did_this]</a></p>
<h1>Batch png to pdf with Inkscape &amp; PDFtk</h1>
<p>Today, I had 57 png images of QR codes, and needed to create a single pdf document where each page contained a single QR code with the file's name shown centered above the QR. From start to finish, without planning ahead, it took me 23 minutes, a pace which would have required me to do 1 image every 24 seconds if I were to do them by hand. Plus, any subsequent tweaks can be done in a flash.</p>
<p>I used <a href="https://inkscape.org/">Inkscape</a> and <a href="https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/">PDFtk</a>.</p>
<ol>
<li><p><strong>Create one svg manually</strong></p>
<ol>
<li>Open Inkscape and adjust the document page size to your liking.</li>
<li>Add the image to the page. I chose to embed the image rather than linking, but in retrospect, linking would have been the correct choice.</li>
<li>Add the text box to the page. With the text selected, press CTRL+SHIFT+T to open the Text and Font menu, choose Center alignment, and click apply.</li>
<li>Arrange the image and label to your liking.</li>
<li>Save the document.</li>
</ol>
</li>
<li><p><strong>Generate svgs for all other images</strong></p>
<ol>
<li>Open the template svg in a text editor and copy everything.</li>
<li><p>Create a Python file, and start by:</p>
<div class="highlight python"><pre><span class="n">TEMPLATE</span> <span class="o">=</span> <span class="s1">'''</span>
<span class="s1">paste the svg file here</span>
<span class="s1"></span><span class="s1">'''</span><span class="n">.</span><span class="n">strip</span><span class="p">(</span><span class="p">)</span>
</pre></div></li>
<li><p>If you chose to embed the image, it will be stored as base64. In that case, delete the actual base64 text, but leave the <code>data:image/png;base64,</code> prefix. After the comma, add <code>{image_base64}</code>.</p>
<p><code>xlink:href="data:image/png;base64,{image_base64}"</code></p>
<p>If you chose to link the image, you'll find the file path. Replace it with <code>{image_path}</code>.</p>
<p><code>xlink:href="{image_path}"</code></p>
</li>
<li><p>Find the element that contains the label text, and replace the text with <code>{label_text}</code>.</p>
</li>
<li><p>The rest of the Python script was as follows:</p>
<div class="highlight python"><pre><span class="kn">import</span> <span class="nn">base64</span>
<span class=""></span><span class="kn">import</span> <span class="nn">glob</span>

<span class=""></span><span class="n">filenames</span> <span class="o">=</span> <span class="n">glob</span><span class="n">.</span><span class="n">glob</span><span class="p">(</span><span class="s1">'</span><span class="s1">*.png</span><span class="s1">'</span><span class="p">)</span>
<span class=""></span><span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">:</span>
    <span class="n">label_text</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">...</span><span class="s1">'</span> <span class="c1"># do some transformations on the filename</span>
    <span class="n">data</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'</span><span class="s1">rb</span><span class="s1">'</span><span class="p">)</span><span class="n">.</span><span class="n">read</span><span class="p">(</span><span class="p">)</span>
    <span class="n">image_base64</span> <span class="o">=</span> <span class="n">base64</span><span class="n">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="n">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'</span><span class="s1">ascii</span><span class="s1">'</span><span class="p">)</span>
    <span class="n">svg</span> <span class="o">=</span> <span class="n">TEMPLATE</span><span class="n">.</span><span class="n">format</span><span class="p">(</span><span class="narg">image_base64</span><span class="o">=</span><span class="n">image_base64</span><span class="p">,</span> <span class="narg">label_text</span><span class="o">=</span><span class="n">label_text</span><span class="p">)</span>
    <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="n">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">'</span><span class="s1">.png</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'</span><span class="s1">.svg</span><span class="s1">'</span><span class="p">)</span><span class="p">,</span> <span class="s1">'</span><span class="s1">w</span><span class="s1">'</span><span class="p">)</span><span class="n">.</span><span class="n">write</span><span class="p">(</span><span class="n">svg</span><span class="p">)</span>
</pre></div><p>Using linking instead of embedding, this would have been more like:</p>
<div class="highlight python"><pre><span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">:</span>
    <span class="n">label_text</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'</span><span class="s1">...</span><span class="s1">'</span> <span class="c1"># do some transformations on the filename</span>
    <span class="n">svg</span> <span class="o">=</span> <span class="n">TEMPLATE</span><span class="n">.</span><span class="n">format</span><span class="p">(</span><span class="narg">image_path</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span> <span class="narg">label_text</span><span class="o">=</span><span class="n">label_text</span><span class="p">)</span>
    <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="n">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">'</span><span class="s1">.png</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'</span><span class="s1">.svg</span><span class="s1">'</span><span class="p">)</span><span class="p">,</span> <span class="s1">'</span><span class="s1">w</span><span class="s1">'</span><span class="p">)</span><span class="n">.</span><span class="n">write</span><span class="p">(</span><span class="n">svg</span><span class="p">)</span>
</pre></div></li>
</ol>
</li>
<li><p><strong>Convert svgs to pdf</strong></p>
<p>On Windows, a for-loop in the command line looks like this:</p>
<p><code>for %x in (*.png) do (start /w inkscape --without-gui --export-pdf=%~nx.pdf %~nx.svg)</code></p>
</li>
<li><p><strong>Combine all pages into single pdf</strong></p>
<p><code>pdftk *.pdf cat output "all.pdf"</code></p>
</li>
</ol>
<p>If I need to make any updates to the template, I can</p>
<ol>
<li>Open any of the svgs and make the changes.</li>
<li>Copy it back into the Python file and redo the <code>{templating}</code>.</li>
<li>Run the python script, inkscape export loop, and pdftk merger.</li>
</ol>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/inkscape_batch_png_pdf/inkscape_batch_png_pdf.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/147587b253cdfcd32a36d67df260934ceaa51a1b"><time datetime="2020-09-20T21:27:33-07:00">2020-09-20</time> Add tag today_i_did_this.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/33e00c9985353008b9f460a92c1049f6f833a39d"><time datetime="2020-07-11T16:37:11-07:00">2020-07-11</time> Use start /w to wait for the process to finish before next one.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/47b98020821918d6b4442f6e59a3f6e776bc231d"><time datetime="2020-06-29T20:40:08-07:00">2020-06-29</time> Add inkscape_batch_png_pdf.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: 56bbc1b95acc9d2c85c97878779196797f50a031/beginner_bubble
title: The Beginner Bubble
url: https://voussoir.net/writing/beginner_bubble
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<h1>The Beginner Bubble</h1><ol id="table_of_contents">Table of contents<li><a href="#introduction">Introduction</a></li><li><a href="#why_generous_interpretation">Why - generous interpretation</a></li><li><a href="#why_critical_interpretation">Why - critical interpretation</a></li><li><a href="#case_studies">Case studies</a></li><li><a href="#escape_the_bubble">Escape the bubble</a></li></ol>
<h2 id="introduction">Introduction<a class="header_anchor_link" href="#introduction"> (§)</a></h2>
<p>The internet has made self-taught learning more accessible than ever before. I am a strong proponent of self-teaching and using the internet to learn about anything you're interested in. But, the nature of internet learning also has its drawbacks, one of which I'm deeming the beginner bubble.</p>
<p>I think many people have had the experience of discovering a niche website written by a single, passionate person with an educational quality that puts textbooks to shame. Take for example <a href="https://ciechanow.ski/lights-and-shadows/">Bartosz Ciechanowski - Lights and Shadows</a>, a phenomenal rundown of the mechanics of light with beautiful interactive modules.</p>
<p>These kinds of websites are great, but they're hard to come by when you're intentionally looking for them. You'll see comments like "I wish I had found this when I was in school!". Outreach is hard for creators, and sifting through SEO and blogspam is hard for learners. Instead, we tend to compromise and use a common arena like Youtube instead of specialized websites and webrings. I would say that Youtube has become the de-facto home of internet learning in many ways, but it suffers greatly from the beginner bubble.</p>
<p>If I were to make a graph depicting the quantity of learning resources available to learners of different skill levels for a topic, which I have, then it would look like this, which it does:</p>
<p><img alt="" loading="lazy" src="/writing/beginner_bubble/graph.svg"/></p>
<p>There seems to be an endless supply of "for beginners", "intro to", and "how to get started with" tutorials online, and a very quick taper into obscurity beyond that. I am not saying that there are literally zero learning resources for advanced and expert learners, but take a quick dive into search queries like
<a href="https://www.youtube.com/results?search_query=programming+oop">programming oop</a>,
<a href="https://www.youtube.com/results?search_query=git+basics">git basics</a>, or
<a href="https://www.youtube.com/results?search_query=learn+hangul">learn hangul</a>
and behold the open floodgates. Now, try again with
<a href="https://www.youtube.com/results?search_query=advanced+python">advanced Python</a> (the first things I see on the page are decidedly still beginner-level: "Overview of Python &amp; How it Works", "Advanced Python 01 - lists / 02 - tuples", ...),
<a href="https://www.youtube.com/results?search_query=advanced+korean+grammar">advanced Korean grammar</a> (notice, it's still mostly vocab lists or unhelpful vlogs).
Higher level learning materials exist, but the number of beginner lessons is so totally dwarfing as to get my attention and provoke this article.</p>
<h2 id="why_generous_interpretation">Why - generous interpretation<a class="header_anchor_link" href="#why_generous_interpretation"> (§)</a></h2>
<p>Let's start by giving everyone the benefit of the doubt.</p>
<p><strong>Never heard of it</strong>: Any topic worth teaching will have some level of pre-requisite knowledge. It is difficult to give a meaningful lesson on Python to someone who doesn't know the basic concept of programming — it will devolve into a lesson on the basic concept of programming that just so happens to be demonstrated in Python. This prerequisite chain continues down to "what is a computer" and so on. So, creating lessons for <em>Never heard of it</em>s is difficult because the creator must assume some level of background knowledge, and every creator may pick a different threshold. By virtue of creating a "Basics of Python, lesson 1", the creator inherently skips over people who have never heard of programming. That's not a bad thing, because nobody on the internet is obligated to create something they don't want to, and most likely there are others out there who will teach the basics of programming.</p>
<p>Not to mention, learners won't be seeking out learning material for topics they've <em>literally</em> never heard of. In order to access that demographic, the creator either has to present basic lessons on many topics and gain a "general learning" or "pop science" subscriber base, or hope that by sharing and word of mouth their lessons will reach the people who will be interested.</p>
<p><strong>Beginner</strong>: Beginner level resources are the most numerous because of multiple natural tendencies by both the learners and creators.</p>
<ol>
<li><p>Learners don't need very specific information and will be happy with anything as long as it's appropriate for their skill level. Each individual creator will have a different idea as to what they should put in their "beginner level" lessons, and the learner will use material from several creators to eventually get a well-rounded beginner-level understanding.</p>
</li>
<li><p>Learners need some time to decide whether or not they want to pursue the field further. They are probably searching for beginner materials just test the waters. After that, learners either drop out or spend more time with hands-on learning, reducing the demand for more advanced tutorial content.</p>
</li>
<li><p>Creators will usually start their lesson series by teaching the basics, instead of going straight for the advanced topics. Though they may continue to create higher level material too, others will stop creating, so the quantity of beginner resources will be the largest of all categories by default. Again, no one has an obligation to create if they don't want to, so someone who tries making tutorials may find they don't want to continue, and that's ok.</p>
</li>
<li><p>Creators who create for the beginner level will all start out in roughly the same place, then later branch out to fulfill specific niches. There comes a point where "Intermediate lesson on topic X" would be better titled "Beginner lesson on more advanced topic Y". For example, a first lesson on machine learning might require intermediate programming skills, but is better described as "Basics of machine learning" than a clumsy "Using intermediate programming to do machine learning". Learning materials will have a tendency to describe themselves as the basics of something rather than the intermediates of something. Thus, the bubble is partly a result of labeling.</p>
</li>
</ol>
<p><strong>Intermediate</strong>: I was careful to title the graph using the phrase "available to" and not "made for", because ultimately no matter how much content is made on the internet, discoverability and searchability is a critical factor in actually reaching anybody. One reason Intermediate material availability is low is that at this level, learning materials are specific enough, and require domain-specific keywords, that simply searching for "intermediate X tutorials" probably won't help the learner find what they're looking for. Any resources that are titled so simply probably contain a smattering of information bordering on beginner-intermediate but without the depth and specificity the learner actually wants. Learners must know some keywords to access what they need, which is difficult when you don't know what you don't know.</p>
<p><strong>Expert</strong>: In addition to the searchability issue, the supply of expert-level lessons is low simply because the demand is low. Experts don't need lessons because they grow through actual experience and they know how to learn from source-level material. An expert programmer doesn't need tutorials on how to use a library because they can read the documentation or source code. An expert artist doesn't need more drawing lessons because they can establish their own style and innovate new techniques.</p>
<h2 id="why_critical_interpretation">Why - critical interpretation<a class="header_anchor_link" href="#why_critical_interpretation"> (§)</a></h2>
<p>It's time to get critical of the situation, because I've noticed behaviors and patterns from creators that don't line up with the generous interpretation. Although the beginner bubble is the observable, physical product of creator behavior, I feel that creator behavior itself can often be described as a beginner <a href="https://en.wikipedia.org/wiki/Tar_pit">tar pit</a>. From the point of view of an internet creator with less-than-noble goals, it is best if the audience remains stuck at the beginner level.</p>
<p>To start with, I accept the fact that the beginner audience is simply the largest audience. I believe what I said about learners and creators dropping out, and learners not needing as many tutorials as they advance. There's nothing wrong with that being true. I don't think the resource graph should be flat. However, I've noticed some creators who stay in the beginner bubble far longer than necessary. I find myself trying to assess whether the creator is genuinely passionate about teaching their subject and helping learners advance their skills, or whether they just make lessons for youtube views, ad dollars, book sales, course sales, etc.</p>
<p>The bubble continues to balloon, because:</p>
<p><strong>Anyone can do it</strong>: The barrier to entry for publishing online is getting lower and lower. Overall, that's a good thing, but it also means that creators can start teaching before they are anything more than a beginner themselves. Some people are just a little too eager to start explaining things to others, and during their second week of learning about a topic will happily make tutorials for everything they learned in their first week. At this stage, the creator lacks the big picture context that would allow them to make smart decisions about what to teach or omit. Their lessons will be regurgitations of whatever lessons they've received, without understanding how the information actually pieces together. I have had to resist this urge many times, and I've failed many others.</p>
<p><strong>Those who can, do</strong>: And those who can't, talk about doing. The only people capable of creating expert-level teaching material are the experts, and where are they? They're out practicing their craft, of course; continuing to hone their skills and furthering their career doing what they're an expert at. Real experts don't have as much time to faff about with internet tutorials as people who like to pretend that they're experts.</p>
<p><strong>They can sell you stuff</strong>: Not only are beginners the largest demographic, they are disproportionately more willing to spend on resources than learners at higher skill levels. This is partly because they haven't reached the moment of disillusionment that learning requires significant time and effort and not just the buying of books, and partly because they cannot yet assess the quality of the resource being sold to them. If a beginner buys a low quality resource that's difficult to learn from, they might think the topic is just hard. Higher level learners will have better BS detectors and less patience for low quality material.</p>
<p>Scammers around the world prey on the ignorant, and "educational" scammers are no different. You can find videos online of magical polyglots spitting phrases in a dozen languages they supposedly learned to fluency in six months each. And for just $14.99, you can have access to their course showing how they got there.</p>
<p>Here's a quote I discovered on <a href="https://news.ycombinator.com/item?id=23451264">hn</a> which is half relevant and all poignant:</p>
<blockquote><p>Writing for money and reservation of copyright are, at bottom, the ruin of literature. No one writes anything that is worth writing, unless he writes entirely for the sake of his subject. What an inestimable boon it would be, if in every branch of literature there were only a few books, but those excellent! This can never happen, as long as money is to be made by writing. It seems as though the money lay under a curse; for every author degenerates as soon as he begins to put pen to paper in any way for the sake of gain. The best works of the greatest men all come from the time when they had to write for nothing or for very little.</p>
<p>If the reader wishes to study any subject, let him beware of rushing to the newest books upon it, and confining his attention to them alone, under the notion that science is always advancing, and that the old books have been drawn upon in the writing of the new. They have been drawn upon, it is true; but how? The writer of the new book often does not understand the old books thoroughly, and yet he is unwilling to take their exact words; so he bungles them, and says in his own bad way that which has been said very much better and more clearly by the old writers, who wrote from their own lively knowledge of the subject</p>
<p>Arthur Schopenhauer, "On Authorship"</p>
</blockquote>
<p><strong>Immunity to criticism</strong>: Resources for beginners will naturally need to smooth over some details to keep the lesson understandable, but that becomes decreasingly acceptable as you reach expert level materials. Creators who can't teach the fine details or don't want to deal with criticism will find safety in the beginner tar pit. As long as the lesson is labeled as "for beginners", then it becomes immune to any criticisms that details are missing, edge cases aren't covered, specifics are left vague, and examples feel artificial. You'll hear "<em>I know I skipped over that, I just wanted to keep it simple</em>".</p>
<p>Not only are beginner resources more immune to criticism, but they attract less criticism in the first place since the audience, by design, consists of people who know little on the subject. It doesn't matter if the magical polyglot horribly mangles the pronunciation of their dozen languages if the listener doesn't know the right pronunciation either. You don't have to be very skilled to impress at this level, which feels good for the creator.</p>
<p><strong>I'm different</strong>: I know I'm a jerk for raining on people's parades, but as I swim through a seemingly endless supply of Lesson Ones, it's clear to me that the majority of creators on the internet are fantastically, myopically unaware of just how many people have already beat them to the punch, or just don't care. It is somewhat mind boggling to me that people don't stop to think "I wonder if anyone else has already made a tutorial about this" before making their own and tossing it onto the mountainous heap. Or they do think about it, but decide that their version is somehow different and special. More realistically, they didn't actually look at what already exists, because they're here to be Mr Teacher, not to learn what's already out there.</p>
<p>Sure, again, we remind ourselves that anyone can make whatever they want. I'm going to stop adding that disclaimer now. It's true that teaching others is a great way to help oneself learn, so maybe a lot of it boils down to that. Still, it's fascinating to me from a sociology point of view that creators either don't consider or realize the oceanic scale of the internet, and the fact that their idea has probably already been done and done better <a class="footnote_link" data-index="1" href="#footnote_text_1" id="footnote_link_1" title="[1] The brain has not yet become accustomed to the light which the internet has shone upon it: it's very, very hard to come up with anything original.">[1]</a>; or they do realize that but go ahead with it anyway.</p>
<p>Is it for ego, to have <em>their</em> name on the page and not someone else's? Is it for practice, to get comfortable writing and speaking and editing? Is it because they'd rather spend 3 hours making their own tutorial than 20 minutes reading another and just sharing that link? Do they genuinely think they bring something new to the table, even on basic and rote topics? But, wait a minute, none of these desires require that we stay in the tar pit — couldn't we get the same ego boost and speaking practice and enjoyment of creation by making something besides another Lesson One? Something actually unique? <a class="footnote_link" data-index="2" href="#footnote_text_2" id="footnote_link_2" title="[2] inb4 this article is not unique either.">[2]</a> The answer is most obviously yes, and yet here we are. The number of collective man-hours spent spinning our wheels on remaking the same Lesson Ones is disappointing. I'm not asking for people to be robotic and completely deferential to the anonymous mass of the internet, with only one tutorial in the whole world for any given subject, but the situation has got my mind turning and I'm becoming increasingly self-aware about whether I'm being novel enough in what I publish.</p>
<p>When I am programming, I often find situations where there is an existing tool to do what I need, but I would rather have the enjoyment and control of making my own. This is surely similar, though I would note that I publish them without making any false gestures that I'm doing it for the benefit of some audience. I publish them because its free and easy and doesn't bother anyone else since my repos get no visitors anyway.</p>
<p><strong>Low commitment</strong>: When it comes to beginner-level information, there's never a single correct order to present the many concepts. Knowledge is a web without a clear starting point. It is natural that a teacher will present many ideas which at first seem unrelated, and the connections become clear as the lessons progress. However, if the creator is not committed to helping learners grow their knowledge web, they have the option of instead continuously bouncing around their repertoire of beginner lessons without actually developing the connections. If the learner doesn't perceive the connections, and every lesson is presented to them as if it were novel, they may not realize they're being held in the tar pit when they could be advancing.</p>
<p>Contrast that with someone who shows, say, how to build a house from the ground up, or how to go <a href="https://www.nand2tetris.org/">from Nand to Tetris</a>. In those cases, there's a clear starting point (empty lot, nand) and a clear ending point (house, tetris), and if the creator doesn't reach the ending point it will be obvious to every onlooker. Abandonment and unfinished business is more apparent at the expert level. Creators who aren't confident or committed in their teaching can stay in the tar pit.</p>
<p><strong>Audience pull</strong>: My tone in this section largely paints these beginner-level resource creators as malicious — as intentionally trying to keep learners stuck in the tar pit for their own personal gain. And for the scammer types that's true, but I must clarify that some creators wind up staying in the tar pit without consciously choosing to. Take for example a creator who makes a couple of beginner lessons, then starts receiving comments and emails from learners requesting a lesson on some other beginner topic. Repeat a few times. The problem is that the collective intelligence of the requests never really rises because the audience churns and new beginners make new requests, and randos on the internet are very bad at using search bars for some reason, so they wind up requesting the same things over and over again. For the creator, it feels good to be getting recognition, and they'll want to please the crowd by fulfilling those requests, without putting their foot down about the direction they want to take their creation. Plus, again, beginners are the biggest audience, creating disproportional feedback towards beginner level topic requests.</p>
<p><a class="footnote_text" data-index="1" href="#footnote_link_1" id="footnote_text_1">[1]</a> The brain has not yet become accustomed to the light which the internet has shone upon it: it's very, very hard to come up with anything original.</p>
<p><a class="footnote_text" data-index="2" href="#footnote_link_2" id="footnote_text_2">[2]</a> inb4 this article is not unique either.</p>
<h2 id="case_studies">Case studies<a class="header_anchor_link" href="#case_studies"> (§)</a></h2>
<p><a href="https://youtube.com/channel/UC5r3WHrX4Z7peSYpDlgktGw/videos">Talk To Me In Korean</a> is the inspiration for this article. With over 1,600 videos already on the channel and some dozens of courses on the site, they continue to astound me by releasing videos like <a href="https://www.youtube.com/watch?v=TUSNTV7VkcI">How to make Korean sentences (for absolute beginners)</a>. In the past six months, they have greatly ramped up production on their "Live Korean Class" series, which are hour-long meanders through a general topic. The individual teaching moments that occur throughout the livestreams will of course be valuable to somebody, but with such low information density, searchability, and conciseness of theme, they are useless to someone serious about learning the language. These are for people who want to feel like they spent an hour studying, without putting in an hour's effort.</p>
<p>Every couple of weeks they publish a video reminding you to buy their books.</p>
<p>TTMIK's best product is undoubtedly Iyagi, a now-defunct podcast which totaled 145 episodes of pure, no-English, normal-speed Korean speech plus full transcripts. At one point, all the episodes were freely available and I downloaded them. Now they're behind a paywall, because TTMIK realized that their podcast from 8-10 years ago is of higher value than the hours and hours of video they make now. Their <a href="https://www.youtube.com/watch?v=cP55GAFNCx8" title="Princess Service">old videos</a> are pretty hilarious, too. As always, it's the business that spoiled the product.</p>
<p><a href="https://www.duolingo.com/">Duolingo</a> has a <a href="https://old.reddit.com/r/languagelearning/search?q=duolingo&amp;restrict_sr=on&amp;include_over_18=on&amp;sort=relevance&amp;t=all">reputation</a> for people using it to study on a daily basis, sometimes for months or years, without making significant meaningful progress. Yes, we can blame the learners for only using a single resource, and only using it for a few minutes a day. But Duolingo's reputation in this regard far overshadows any similar examples I can think of. There's clearly something about Duolingo's formula that encourages the learner to feel as if they're being productive without actually helping them out of the bubble. Part of that comes down to their advertising, and part of it is their UI's positive tone and encouragement without reminders to study harder with other resources too.</p>
<p>Forgive me if I don't provide too many other case studies of bad examples. I don't actually want to bully individuals, and these days I don't read / watch enough tutorial content to know names of popular creators. TTMIK just brought it out of me. Let's share some positives instead.</p>
<p><a href="https://www.youtube.com/channel/UCG-KntY7aVnIGXYEBQvmBAQ">Thomas Frank</a> has created a respectable channel and I am happy for his success. Spreading the word about learning and motivation is nothing but a good thing. You can tell, though, that his video topics tend to repeat, probably partially as a result of fan requests. For a general topic like "college level studying" of course I expect everything to stay rather basic and widely applicable, but I think Frank's channel is a good example of the pressure to publish exceeding the number of new topics to explore in the space.</p>
<p><a href="https://youtube.com/channel/UC7ynjArlSFuXW23ssZAknyA/videos">GO! Billy Korean</a> is a great guy and I admire his passion for teaching. He usually doesn't get into highly advanced lessons, but he gets into intermediates with his language and culture trends videos. He even goes through the effort of cutting down his live 2-hour classes into 12-minute abridged versions, which I know for a fact is not a quick thing to do. His videos are straight to the point and he very, very rarely mentions his paid books or patreon. His primary lesson videos are short enough and well-titled. My only criticism is that he seems to have put a hold on his primary series of 113 episodes to start a new <a href="https://www.youtube.com/watch?v=uOlcIBtaplQ">beginner course</a>, which is a bit of a circle back into the tar pit.</p>
<p><a href="https://youtube.com/channel/UCgHxZ3lZwfxQcr-P6HuU7mA/videos">ultimateKOREAN</a> is a champion of lesson quality. How often do you see actual bibliographic citations in a youtube video description? Although the specific dialogues that he teaches with are often above my level, his explanations give me clear insight on what I should look out for as I continue to learn. He is careful to note that grammar taught in textbooks is not the same as grammar used in real life, and always presents multiple possible reasons why a certain grammar construction would be used. In the comments of <a href="https://www.youtube.com/watch?v=Lzi1jzpT6XI">A deep look at 잖아</a>, he says "I love doing this but at the same time I somewhat feel discouraged when other channels get more views for less informative content". Please show him your appreciation.</p>
<p><a href="https://youtube.com/channel/UCivA7_KLKWo43tFcCkFvydw/videos">Ben Krasnow aka Applied Science</a> doesn't make 'lessons' per se, but he is undeniably a natural teacher. Everything he does is filled with genuine interest and the utmost respect for the viewers. He knows exactly how to progress the viewer's understanding from Never Heard of It to a reasonable understanding (for a sub-30 minute video) without ever dumbing down the scientific terminology. When he's experimenting with something, he always points out room for improvement and possible future applications to stimulate the imagination.</p>
<p><a href="https://youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw/videos">3Blue1Brown</a> makes animated math videos and <a href="https://github.com/3b1b/manim">publishes the code he makes them with</a>. His goal is to clear away the magical haze that blocks peoples' understanding of math by showing how complicated systems can be represented with clear, intuitive visuals. His lessons are awesome and he's awesome.</p>
<p><a href="https://youtube.com/channel/UC2bkHVIDjXS7sgrgjFtzOXQ/videos">Bill Hammack aka The Engineer Guy</a> is a clear communicator who shows the fascinating internals and design of things we take for granted, often with a poetic bent. It's hard to pick a single best video, so check out <a href="https://www.youtube.com/watch?v=wI-qAxKJoSU">Nitinol</a>, <a href="https://www.youtube.com/watch?v=RMjtmsr3CqA">Plastic injection molding</a> and <a href="https://www.youtube.com/watch?v=hUhisi2FBuw">Aluminum cans</a>.</p>
<p><a href="https://youtube.com/channel/UC1T4KJG1L_kTrP9RcdU5Csw/videos">Ian LaSpina aka Knyght Errant</a> participates in Medieval Living History events and makes videos about medieval culture, arms, and armor. Although I never considered myself to be very interested in history, I was absolutely absorbed by his <a href="https://www.youtube.com/watch?v=mSjmxMfEtvE">Palazzo Ducale Bascinet video</a> and quickly <a href="https://git.voussoir.net/voussoir/ycdl#easily-watch-every-video-on-the-channel">binged</a> his whole channel.</p>
<h2 id="escape_the_bubble">Escape the bubble<a class="header_anchor_link" href="#escape_the_bubble"> (§)</a></h2>
<p>To creators who are passionate about teaching: Know that there is an audience who desires higher level learning material. Know that a thoughtful and powerful lesson maintains its value and rewatchability for a long time, although the overall view count will be lower than the cheap stuff. Rewatching well-presented lessons is a pleasure in itself, and I frequently rewatch videos on the channels mentioned here just for fun. Know that as the creator, your channel is your ship to steer and you don't need to be pulled back by audience requests.</p>
<p>To creators who are passionate about making money: Nothing I can say will change your mind.</p>
<p>To learners: Recognize when you are being held in a tar pit. Diversify your learning sources so as to know the strengths and weaknesses of each. Encourage creators who do good work, because they need to hear your appreciation. Support them on patreon etc. if you want to go the extra mile.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/beginner_bubble/beginner_bubble.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/3475cb0abf63172e6392487ffaf41755f71a18e3"><time datetime="2023-09-18T21:01:09-07:00">2023-09-18</time> Replace github links with git.voussoir.net.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/dfea7417b29e5d258162add171917e3e564569f3"><time datetime="2021-01-02T23:39:47-08:00">2021-01-02</time> Fix typo "his goal it to".</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/99ce0997616aacb5f052ea89cdb2a08e9b710402"><time datetime="2020-09-19T19:37:02-07:00">2020-09-19</time> Add link to Lights and Shadows.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/bf989111ff8e6bc1c7252f28accc094bb14635cb"><time datetime="2020-08-28T16:53:23-07:00">2020-08-28</time> Add link to an old TTMIK video.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/7fee5d031f01199acf70db5b8923fab037332ca5"><time datetime="2020-08-10T20:46:22-07:00">2020-08-10</time> Rename voussoir/YoutubeChannelDownloader -&gt; voussoir/ycdl.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/56bbc1b95acc9d2c85c97878779196797f50a031"><time datetime="2020-08-06T14:34:30-07:00">2020-08-06</time> Add beginner_bubble.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


guid: bb10ae9b2dbecdb6668ddccddde5407fe438ac1a/running_python_scripts
title: Running Python scripts
url: https://voussoir.net/writing/running_python_scripts
feedurl: https://voussoir.net/writing/writing.atom
content_mime_type: text/html
content:
            
            <article>
<p><a href="/writing">Back to writing</a></p>

<p><a class="tag_link" data-qualname="python" href="/writing/tags/python">[python]</a> <a class="tag_link" data-qualname="tutorial" href="/writing/tags/tutorial">[tutorial]</a></p>
<h1>Running Python scripts</h1><ol id="table_of_contents">Table of contents<li><a href="#level_0_pythonexe_myfilepy">Level 0: python.exe myfile.py</a></li><li><a href="#level_1_python_myfilepy">Level 1: python myfile.py</a></li><li><a href="#level_2_myfilepy">Level 2: myfile.py</a></li><li><a href="#level_3_myfilepy_on_the_path">Level 3: myfile.py on the PATH</a></li><li><a href="#level_4_myfile">Level 4: myfile</a></li></ol>
<p>In this document, <code>C:\python</code> is the folder where you installed Python to. That's where <code>python.exe</code> lives. <code>C:\code</code> is a folder you created where you put all the <code>.py</code> files that you make. Lastly, <code>C:\anywhere</code> is anywhere else on your computer that is not one of these two locations.</p>
<p>I am using Windows style prompts with <code>cwd&gt;</code>. For Linux users, this is the same as your <code>user:cwd$</code>.</p>
<h2 id="level_0_pythonexe_myfilepy">Level 0: <code>python.exe myfile.py</code><a class="header_anchor_link" href="#level_0_pythonexe_myfilepy"> (§)</a></h2>
<p>In this level, you are specifying the exact location of the Python executable and the exact location of the script.</p>
<p><code>C:\python&gt; python.exe C:\code\myfile.py</code></p>
<p><code>C:\code&gt; C:\python\python.exe myfile.py</code></p>
<p><code>C:\anywhere&gt; C:\python\python.exe C:\code\myfile.py</code></p>
<h2 id="level_1_python_myfilepy">Level 1: <code>python myfile.py</code><a class="header_anchor_link" href="#level_1_python_myfilepy"> (§)</a></h2>
<p>In this level, you are letting the system find Python, but specifying the exact location of the script.</p>
<p>When you install Python, there is an option to add Python to your PATH. The PATH is a list of directories that your computer will search every time you type a command on the command prompt. This means that when you type <code>python</code>, it will automatically find the python.exe in your installation folder. Windows users, type "environment variable" into your Windows search bar to open the settings menu and find the variable called PATH, or <code>echo %PATH%</code> on the cmd to see what it looks like. Linux users, <code>echo $PATH</code>.</p>
<p><code>C:\code&gt; python myfile.py</code></p>
<p><code>C:\anywhere&gt; python C:\code\myfile.py</code></p>
<h2 id="level_2_myfilepy">Level 2: <code>myfile.py</code><a class="header_anchor_link" href="#level_2_myfilepy"> (§)</a></h2>
<p>In this level, you are specifying the exact location of the script, but do not mention Python at all, because the system knows it is a Python file.</p>
<p><strong>Windows:</strong> Ensure that <code>.py</code> file extensions are associated with the python.exe or py.exe that you installed. This should happen automatically when you install Python, and you can always re-run the Python installer to reset these associations if they become broken.</p>
<p><code>C:\code&gt; myfile.py</code></p>
<p><code>C:\anywhere&gt; C:\code\myfile.py</code></p>
<p><strong>Linux:</strong> Add a shebang for your Python executable to the top of the script file, and mark the file as executable with <code>chmod +x myfile.py</code>.</p>
<p><code>/code$ ./myfile.py</code></p>
<p><code>/anywhere$ /code/myfile.py</code></p>
<h2 id="level_3_myfilepy_on_the_path">Level 3: <code>myfile.py</code> on the PATH<a class="header_anchor_link" href="#level_3_myfilepy_on_the_path"> (§)</a></h2>
<p>In this level, you do not need to specify the exact path of the script ever.</p>
<p>Add the directory that contains the script, <code>C:\code</code>, to your PATH. Remember that PATH is a list of directories, not files. If you add <code>C:\code\myfile.py</code>, it won't work. You have to add the directory.</p>
<p><code>C:\anywhere&gt; myfile.py</code></p>
<h2 id="level_4_myfile">Level 4: <code>myfile</code><a class="header_anchor_link" href="#level_4_myfile"> (§)</a></h2>
<p>The final level is to run Python scripts without specifying Python, or the extension, or the file's location. At this level, Python scripts feel like native commands such as <code>dir</code>, <code>ls</code>, and <code>cd</code>.</p>
<p><strong>Both</strong>: Add the directory that contains the script to your PATH.</p>
<p><strong>Windows</strong>: Add <code>.py</code> to your PATHEXT variable, so Windows will understand that when you type <code>myfile</code>, it should also search the PATH for <code>myfile.py</code>. Use <code>where myfile</code> to double check that your file appears, and there is not another program with the same name taking priority.</p>
<p><strong>Linux</strong>: Keep the shebang at the top of the file, and rename the file to remove the <code>.py</code> extension. Use <code>which myfile</code> to double check that it is the top result.</p>
<p><code>C:\anywhere&gt; myfile</code></p>
<p>The only way to get better than this is to have a mind-controlled computer where you don't have to type anything.</p>

<hr/>
<p><a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/running_python_scripts/running_python_scripts.md">View this document's history</a></p>
<ul>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/f2e054a12370f7dc74328690ee21eb9821863db1"><time datetime="2020-08-20T10:50:48-07:00">2020-08-20</time> Replace level 2.5 with 3, then 3 with 4.</a></li>
<li><a href="https://git.voussoir.net/voussoir/voussoir.net/commit/bb10ae9b2dbecdb6668ddccddde5407fe438ac1a"><time datetime="2020-08-13T14:46:40-07:00">2020-08-13</time> Add running_python_scripts.md.</a></li>
</ul>
<address>Contact me: writing@voussoir.net</address>
<p>If you would like to subscribe for more, add this to your RSS reader: <a href="/writing/writing.atom" rel="alternate" type="application/atom+xml">https://voussoir.net/writing/writing.atom</a></p>
</article>
            
            


