| CARVIEW |
Select Language
HTTP/1.1 200 OK
Date: Thu, 15 Jan 2026 23:25:16 GMT
Server: Apache/2.4.41 (Ubuntu)
Last-Modified: Sun, 21 Sep 2025 15:04:26 GMT
ETag: "1d951-63f5104dbe49f-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 35674
Content-Type: application/rss+xml
The Universe of Discourse
https://blog.plover.com
The Universe of Discourse (Mark Dominus Blog)
en
-
My new git utility `what-changed-twice` needs a new name
https://blog.plover.com/2025/09/21#what-changed-twice
<p>As I have <a href="https://blog.plover.com/prog/git-habits.html">explained in the past</a>, my typical workflow is to go
along commiting stuff that might or might not make sense, then clean it
all up at the end, doing multiple passes with <code>git-add</code> and <code>git-rebase</code>
to get related changes into the same commit, and then to order the
commits in a sensible way. Yesterday I built a new utility that I found
helpful. I couldn't think of a name for it, so I called it
<code>what-changed-twice</code>, which is not great but my I am bad at naming
things and my first attempt was <code>analyze-commits</code>. I welcome
suggestions. In this article I will call it Fred. </p>
<p>What is Fred for? I have a couple of uses for it so far.</p>
<p>Often as I work I'll produce a chain of commits that looks like
this:</p>
<pre><code>470947ff minor corrections
d630bf32 continue work on `jq` series
c24b8b24 wip
f4695e97 fix link
a8aa1a5c sp
5f1d7a61 WIP
a337696f Where is the quincunx on the quincunx?
39fe1810 new article: The fivefold symmetry of the quince
0a5a8e2e update broken link
196e7491 sp
bdc781f6 new article: fpuzhpx
40c52f47 merge old and new seasons articles and publish
b59441cd finish updating with Star Wars Droids
537a3545 droids and BJ and the Bear
d142598c Add nicely formatted season tables to this old article
19340470 mention numberphile video
</code></pre>
<p>It often happens that I will modify a file on Monday, modify it some
more on Tuesday, correct a spelling error on Wednesday. I might have
made 7 sets of changes to the main file, of which 4 are related, 2
others are related to each other but not to the other 4, and the last
one is unrelated to any of the rest. When a file has changed more than
once, I need to see what changed and then group the changes into related
sets.</p>
<p>The <code>sp</code> commits are spelling corrections; if the error was made in the
same unmerged topic branch I will want to squash the correction into
the original commit so that the error never appears at all.</p>
<p>Some files changed only once, and I don't need to think about those at
this stage. Later I can go back and split up those commits if it seems
to make the history clearer.</p>
<p>Fred takes the output of <code>git-log</code> for the commits you are interested
in:</p>
<pre><code>$ git log --stat -20 main...topic | /tmp/what-changed-twice
</code></pre>
<p>It finds which files were modified in which commits, and it prints a
report about any file that was modified in more than one commit:</p>
<pre><code> calendar/seasons.blog 196 40 d1
math/centrifuge.blog 193 33
misc/straight-men.blog 53 b5 bd
prog/jq-2.blog 33 5f d6
193 1934047
196 196e749
33 33a2304
40 40c52f4
53 537a354
5f 5f1d7a6
b5 b59441c
bd bdc781f
d1 d142598
d6 d630bf3
</code></pre>
<p>The report is in two parts. At the top, the path of each file that
changed more than once in the log, and the (highly-abbreviated) commit
IDs of the commits in which it changed. For example,
<code>calendar/seasons.blog</code> changed in commits <code>196</code>, <code>40</code>, and <code>d1</code>. The
second part of the report explains that <code>196</code> is actually an
abbreviation for commit <code>196e749</code>.</p>
<p>Now I can look to see what else changed in those three commits:</p>
<pre><code>$ git show --stat 196e749 40c52f4 d142598
</code></pre>
<p>then look at the changes to <code>calendar/seasons.blog</code> in those three
commits</p>
<pre><code>$ git show 196e74 40c52f4 d142598 -- calendar/seasons.blog
</code></pre>
<p>and then decide if there are any changes I might like to squash
together.</p>
<p>Many other files changed on the branch, but I only have to concern
myself with four.</p>
<p>There's bonus information too. If a commit is <em>not</em> mentioned in the
report, then it only changed files that didn't change in any other
commit. That means that in a rebase, I can move that commit literally
anywhere else in the sequence without creating a conflict. Only the
commits in the report can cause conflicts if they are reordered.</p>
<p>I write most things in Python these days, but this one seemed to cry out for Perl. <a href="https://github.com/mjdominus/git-util/blob/master/bin/what-changed-twice">Here's the code</a>.</p>
<p>Hmm, maybe I'll call it <code>squash-what</code>.</p>
-
Mystery of the quincunx's missing quincunx
https://blog.plover.com/2025/05/25#quincunx
<p>A <em>quincunx</em> is the X-shaped pattern of pips on the #5 face of a die.</p>
<p><a href="https://pic.blog.plover.com/history/quincunx/x.png"><img class="center" border=0 src="https://pic.blog.plover.com/history/quincunx/x-th.png" alt="A square with five dots arranged in an X" /></a></p>
<p>It's so-called because the Romans had a common copper coin called an
<em>as</em>, and it was divided (monetarily, not physically) into twelve
<em>uncia</em>. There was a bronze coin worth five uncia called a <em>quīncunx</em>, which
is a contraction of <em>quīnque</em> (“five”) + <em>uncia</em>, and the coin had
that pattern of dots on it to indicate its value.</p>
<p><em>Uncia</em> generally meant a twelfth of something. It was not just a
twelfth of an <em>as</em>, but also a twelfth of a pound , which is where we
get the word “ounce”, and a twelfth of a foot, which is where we get
the word “inch”.</p>
<p>The story I always heard about the connection between the coin and the
X-shaped pattern of dots was the one that is told by Wikipedia:</p>
<blockquote>
<p>Its value was sometimes
represented by a pattern of five dots arranged at the corners and the
center of a square, like the pips of a die. So, this pattern also came
to be called quincunx.</p>
</blockquote>
<p>Or the Big Dictionary:</p>
<blockquote>
<p>… [from a] coin of this value (occasionally marked with a pattern
resembling the five spots on a dice cube),…</p>
</blockquote>
<p>But today I did Google image search for qunicunxes. And while most
had five dots, I found not even one that had the dots arranged in an
X pattern.</p>
<p><a href="https://pic.blog.plover.com/history/quincunx/coin1.png"><img class="center" border=0
src="https://pic.blog.plover.com/history/quincunx/coin1-th.png" alt="Pictures of the two sides of an
ancient, corroded, worn, weathered coin. Each one has a four-armed
cross who arms have crossbars at the ends, and the one on the right
also has five dots. The dots are in a cluster in the space between
the cross's lower and left arms, and are arranged in a row of three
and, closer to the center, a row of two." /></a></p>
<p><a href="https://pic.blog.plover.com/history/quincunx/coin2.png"><img class="center" border=0
src="https://pic.blog.plover.com/history/quincunx/coin2-th.png" alt="Another cruddy coin. The obverse
shows the head of a person, probably Minerva, wearing a plumed helmet.
Above the head is a
row of five dots." /></a></p>
<p><a href="https://pic.blog.plover.com/history/quincunx/coin3.png"><img class="center" border=0
src="https://pic.blog.plover.com/history/quincunx/coin3-th.png" alt="This coin is covered with green
oxide. The obverse is another helmeted Minerva, surmounted by a
horizontal row of five dots. The reverse has a picture of an owl,
and, on the right, a column of five dots." /></a></p>
<p>(I believe the heads here are Minerva, goddess of wisdom. The owl is
also associated with Minerva.)</p>
<p>Where's the quincunx that actually has a quincuncial arrangement of
dots? Nowhere to be found, it seems. But everyone says it, so it must be true.
<!-- come on now, they wouldn't let it on the Internet if it wasn't true! --></p>
<h2>Addenda</h2>
<ul>
<li><p>The first common use of “quincunx” as an <em>English</em> word was to refer
to trees that were planted in a quincuncial pattern, although not
necessarily in groups of exactly five, in which each square of four
trees had a fifth at its center.</p></li>
<li><p>Similarly, the
<a href="https://en.wikipedia.org/wiki/File:Quincunx_(Galton_Box)_-_Galton_1889_diagram.png">Galton Box</a>,
has a quincuncial arrangement of little pegs. Galton himself called
it a “quincunx”.</p></li>
<li><p>The OED also offers this fascinating aside:</p>
<blockquote>
<p><strong>Latin</strong> <em>quincunx</em> occurs earlier in an English context. Compare
the following use apparently with reference to a v-shaped figure: </p>
<p><strong>1545</strong> <em>Decusis</em>, tenne hole partes or ten Asses...It is also a
fourme in any thynge representyng the letter, <em>X</em>, whiche parted in
the middel, maketh an other figure called <em>Quincunx</em>, <em>V</em>. </p>
</blockquote>
<p>which shows that for someone, a quincuncial shape was a V and not
an X, presumably because V is the Roman numeral for five.</p>
<p>A <em>decussis</em> was a coin worth not ten <em>uncia</em> but ten <em>asses</em>, and
it did indeed have an X on the front. A five-<em>as</em> coin was a
<em>quincussis</em> and it had a V. I wonder if the author was confused?</p>
<p>The source is
<a href="https://archive.org/details/bim_early-english-books-1475-1640_bibliotheca-eliot-_elyot-sir-thomas_1545"><em>Bibliotheca Eliotæ</em></a>.
The OED does not provide a page number.</p></li>
<li><p>It wasn't until <em>after</em> I published this that I realized that
today's date was the extremely quincuncial 2025-05-25. I thank the
gods of chance and fortune for this little gift.</p></li>
</ul>
-
The fivefold symmetry of the quince
https://blog.plover.com/2025/05/23#quince
<p>The quince is so-named because, like other fruits in the apple family,
it has a natural fivefold symmetry:</p>
<p><a href="https://pic.blog.plover.com/bio/quince/quince.png"><img class="center" border=0
src="https://pic.blog.plover.com/bio/quince/quince-th.png" alt="several greenish-yellow quinces.
They are like shiny pears, but less elongated. In the foreground, one
is cut in half, to reveal five wedge-shaped hollows arranged
symmetrically to form a circle, each filled with shiny brown seeds." /></a></p>
<p>This is because their fruits develop from five-petaled flowers, and
the symmetry persists through development. These are pear blossoms:</p>
<p><a href="https://pic.blog.plover.com/bio/quince/pear-blossoms.png"><img class="center" border=0
src="https://pic.blog.plover.com/bio/quince/pear-blossoms-th.png" alt="A small branch from a pear
tree, with green leaves and white pear blossoms. The bossoms have
five petals each, against which a cluster of dark-tipped stamens
contrasts." /></a></p>
<p>You can see this in most apples if you cut them into equatorial slices:</p>
<p><a href="https://pic.blog.plover.com/bio/quince/cut-apples.png"><img class="center" border=0
src="https://pic.blog.plover.com/bio/quince/cut-apples-th.png" alt="Apple slices on a cutting
board, each with a hole in the middle from the seed capsule in the
center of the core, in the shape of a five-pointed star." /></a></p>
<p>The fivefold symmetry isn't usually apparent from the outside once the
structure leaves the flowering stage. But perfect Red Delicious
specimens do have five little feet:</p>
<p><a href="https://pic.blog.plover.com/bio/quince/rs-apples.png"><img class="center" border=0
src="https://pic.blog.plover.com/bio/quince/rs-apples-th.png" alt="A dozen Red Delicious apples,
bottoms up to show that each does have five little bumps arranged
around the blossom end." /></a></p>
<p>P.S.: I was just kidding about the name of the quince, which actually
has nothing to do with any of this. It is a coincidence.</p>
-
A descriptive theory of seasons in the Mid-Atlantic
https://blog.plover.com/2025/05/08#seasons
<p>[ I started thinking about this about twenty years ago, and then writing it down in 2019, but it seems to be obsolete. I am publishing it anyway. ]</p>
<p><link href="https://pic.blog.plover.com/calendar/seasons/style2.css" rel="stylesheet" /></p>
<p>The canonical division of the year into seasons in the northern
temperate zone goes something like this:</p>
<blockquote>
<ul>
<li>Spring: March 21 – June 21</li>
<li>Summer: June 21 – September 21</li>
<li>Autumn: September 21 – December 21</li>
<li>Winter: December 21 – March 21</li>
</ul>
</blockquote>
<p>Living in the mid-Atlantic region of the northeast U.S., I have never
been happy with this. It is just not a good description of the
climate.</p>
<p>I begin by observing that the year is not equally partitioned between
the four seasons. The summer and winter are longer, and spring and
autumn are brief and happy interludes in between.</p>
<p>I have no problem with spring beginning in the middle of March. I
think that is just right. March famously comes in like a lion and
goes out like a lamb. The beginning of March is crappy, like
February, and frequently has snowstorms and freezes. By the end of
March, spring is usually skipping along, with singing birds and not just the early
flowers (snowdrops, crocuses, daffodil) but many of the later ones also.</p>
<p>By the middle of May the spring flowers are over and the weather is
getting warm, often uncomfortably so. Summer continues through the
beginning of September, which is still good for swimming and
lightweight clothes. In late September it finally gives way to
autumn.</p>
<p>Autumn is jacket weather but not overcoat weather. Its last gasp is
in the middle of November. By this time all the leaves have changed,
and the ones that are going to fall off the trees have done so. The
cool autumn mist has become a chilly winter mist. The cold winter
rains begin at the end of November.</p>
<p>So my first cut would look something like this:</p>
<div class="seasonal-calendar" style="grid-template-columns: 1fr 2fr; width: 60%;">
<!-- Headers -->
<div class="header months-header">Months</div>
<div class="header seasons-header">Seasons</div>
<!-- Months -->
<div class="month jan">January</div>
<div class="month feb">February</div>
<div class="month mar">March</div>
<div class="month apr">April</div>
<div class="month may">May</div>
<div class="month jun">June</div>
<div class="month jul">July</div>
<div class="month aug">August</div>
<div class="month sep">September</div>
<div class="month oct">October</div>
<div class="month nov">November</div>
<div class="month dec">December</div>
<!-- Main Seasons -->
<div class="season winter1">Winter</div>
<div class="season spring">Spring</div>
<div class="season summer">Summer</div>
<div class="season autumn">Autumn</div>
<div class="season winter2">Winter</div>
</div>
<p>Note that this puts Thanksgiving where it belongs at the boundary
between autumn (harvest season) and winter (did we harvest enough to
survive?). Also, it puts the winter solstice (December 21) about one
quarter of the way through the winter. This is correct. By the
solstice the days have gotten short, and after that the cold starts to
kick in. (“As the days begin to lengthen, the cold begins to
strengthen”.) The conventional division takes the solstice as the
<em>beginning</em> of winter, which I just find perplexing. December 1 is
not the very coldest part of winter, but it certainly isn't autumn.</p>
<p>There is <em>something</em> to be said for it though. I think I can
distinguish several subseasons — ten in fact:</p>
<h2>Dominus Seasonal Calendar</h2>
<div class="seasonal-calendar">
<!-- Headers -->
<div class="header months-header">Months</div>
<div class="header seasons-header">Seasons</div>
<div class="header subseasons-header">Sub-seasons</div>
<!-- Months -->
<div class="month jan">January</div>
<div class="month feb">February</div>
<div class="month mar">March</div>
<div class="month apr">April</div>
<div class="month may">May</div>
<div class="month jun">June</div>
<div class="month jul">July</div>
<div class="month aug">August</div>
<div class="month sep">September</div>
<div class="month oct">October</div>
<div class="month nov">November</div>
<div class="month dec">December</div>
<!-- Main Seasons -->
<div class="season winter1">Winter</div>
<div class="season spring">Spring</div>
<div class="season summer">Summer</div>
<div class="season autumn">Autumn</div>
<div class="season winter2">Winter</div>
<!-- Sub-seasons -->
<div class="sub-season midwinter1">Midwinter</div>
<div class="sub-season late-winter">Late Winter</div>
<!-- Placeholders for the rest of the sub-seasons -->
<div class="sub-season early-spring">Early spring</div>
<div class="sub-season late-spring">Late spring</div>
<div class="sub-season early-summer">Early Summer</div>
<div class="sub-season midsummer">Midsummer</div>
<div class="sub-season late-summer">Late Summer</div>
<div class="sub-season early-autumn">Early autumn</div>
<div class="sub-season late-autumn">Late autumn</div>
<div class="sub-season early-winter">Early winter</div>
<div class="sub-season midwinter2">Midwinter</div>
</div>
<p>Midwinter, beginning around the solstice, is when the really crappy
weather arrives, day after day of bitter cold. In contrast, early and
late winter are typically much milder. By late February the snow is
usually starting to melt. (March, of course, is always unpredictable,
and usually has one nasty practical joke hiding up its sleeve. Often,
March is pleasant and springy in the second week, and then mocks you
by turning back into January for the third week. This takes people by
surprise almost <em>every year</em> and I wonder why they never seem to catch
on.)</p>
<p>Similarly, the really hot weather is mostly confined to
midsummer. Early and late summer may be warm but you do not get
blazing sun and you have to fry your eggs indoors, not on the
pavement.</p>
<p>Why the seasons seem to turn in the middle of each month, and not at
the beginning, I can't say. Someone messed up, but who? Probably the
Romans. I hear that the Persians and the Baha’i start their year on
the vernal equinox. Smart!</p>
<p>Weather in other places is very different, even in the temperate
zones. For example, in southern California they don't have any of the
traditional seasons. They have a period of cooler damp weather in the
winter months, and then instead of summer they have a period of gloomy
haze from June through August.</p>
<h3>However</h3>
<p>I may have waited too long to publish this article, as climate change
seems to have rendered it obsolete. In recent years, we have barely
had midwinter, and instead of the usual two to three annual snows we
have zero. Midsummer has grown from two to four months, and summer
now lasts into October.</p>
-
Claude and I write a utility program
https://blog.plover.com/2025/05/02#claude-xar
<h2>Then I had two problems…</h2>
<!-- alternate title: Fucking `xargs`, how does it work? -->
<p>A few days ago I got angry at <code>xargs</code> for the hundredth time, because
for me <code>xargs</code> is one of those "then he had two problems" technologies.
It never does what I want by default and I can never remember how to
use it. This time what I wanted wasn't complicated: I had a bunch of
PDF documents in <code>/tmp</code> and I wanted to use GPG to encrypt some of
them, something like this:</p>
<pre><code> gpg -ac $(ls *.pdf | menupick)
</code></pre>
<p><a href="https://github.com/mjdominus/util/blob/master/bin/menupick"><code>menupick</code></a>
is a lovely little utility that reads lines from standard input,
presents a menu, prompts on the terminal for a selection from the
items, and then prints the selection to standard output. Anyway, this
didn't work because some of the filenames I wanted had spaces in them,
and <a href="https://blog.plover.com/Unix/whitespace.html">the shell sucks</a>. Also because
<code>gpg</code> probably only does one file at a time.</p>
<p>I could have done it this way:</p>
<pre><code> ls *.pdf | menupick | while read f; do gpg -ac "$f"; done
</code></pre>
<p>but that's a lot to type. I thought “aha, I'll use <code>xargs</code>.” Then I
had two problems.</p>
<pre><code> ls *.pdf | menupick | xargs gpg -ac
</code></pre>
<p>This doesn't work because <code>xargs</code> wants to batch up the inputs to run
as few instances of <code>gpg</code> as possible, and <code>gpg</code> only does one file at
a time. I glanced at the <code>xargs</code> manual looking for the "one at a
time please" option (which should have been the default) but I didn't
see it amongst the forest of other options. </p>
<p>I think now that I needed <code>-n 1</code> but I didn't find it immediately, and
I was tired of looking it up every time when it was what I wanted
every time. After many years of not remembering how to get <code>xargs</code> to
do what I wanted, I decided the time had come to write a stripped-down
replacement that just did what I wanted and nothing else.</p>
<p>(In hindsight I should perhaps have looked to see if <code>gpg</code>'s
<code>--multifile</code> option did what I wanted, but it's okay that I didn't,
this solution is more general and I will use it over and over in
coming years.)</p>
<h2><code>xar</code> is a worse version of <code>xargs</code>, but worse is better (for me)</h2>
<p>First I wrote a comment that specified the scope of the project:</p>
<pre><code> # Version of xargs that will be easier to use
#
# 1. Replace each % with the filename, if there are any
# 2. Otherwise put the filename at the end of the line
# 3. Run one command per argument unless there is (some flag)
# 4. On error, continue anyway
# 5. Need -0 flag to allow NUL-termination
</code></pre>
<p>There! It will do <em>one</em> thing well, as Brian and Rob commanded us in
the Beginning Times.</p>
<p>I wrote a draft implementation that did not even do all those things,
just items 2 and 4, then I fleshed it out with item 1. I decided that
I would postpone 3 and 5 until I needed them. (5 at least isn't a
YAGNI, because I know I have needed it in the past.)</p>
<p>The result was this:</p>
<pre><code> import subprocess
import sys
def command_has_percent(command):
for word in command:
if "%" in word:
return True
return False
def substitute_percents(target, replacement):
return [ s.replace("%", replacement) for s in target ]
def run_command_with_filename(command_template, filename):
command = command_template.copy()
if not command_has_percent(command):
command.append("%")
res = subprocess.run(substitute_percents(command, filename), check=False)
return res.returncode == 0
if __name__ == '__main__':
template = sys.argv[1:]
ok = True
for line in sys.stdin:
if line.endswith("\n"):
line = line[:-1]
if not run_command_with_filename(template, line):
ok = False
exit(0 if ok else 1)
</code></pre>
<p>Short, clean, simple, easy to use. I called it <a href="https://github.com/mjdominus/util/blob/master/bin/xar"><code>xar</code></a>, ran</p>
<pre><code> ls *.pdf | menupick | xar gpg -ac
</code></pre>
<p>and was content.</p>
<h2>Now again, with Claude</h2>
<p>The following day I thought this would be the perfect opportunity to
try getting some LLM help with programming. I already had a baseline
version of <code>xar</code> working, and had thought through the problem
specification. Now I could do it over with the LLM and compare the
two results. The program I wanted was small and self-contained. If
the LLM sabotaged me somehow, I would probably notice, and if I
didn't, it wouldn't matter, because I had a working version
already.</p>
<p>So I asked Claude about it. I knew Claude was not the right choice,
because Claude is not really designed for this task, and has no
integration with the coding editor, but Claude is what I am familiar
with so I thought I'd give it a shot.</p>
<p>On the whole it went extremely well.
<a href="https://claude.ai/share/5749f96b-aaa1-401d-b9e1-fac8f4d7a9bb">The complete transcript is here</a>. I
imagine this was pretty much a maximally good experience, that all the
terrible problems of LLM coding arise in larger, more complicated
systems. But this program is small and self-contained, with no
subtleties, so the LLM could be at its best.</p>
<h2>Phase 1</h2>
<p>First I just told Claude what I wanted and let it write the program
from scratch.</p>
<blockquote>
<p>Let's write a program in Python called <code>xar</code>. It will be a
stripped-down version of the standard Unix <code>xargs</code> utility, but it
will do less and be simpler to use.</p>
<p>It will get command-line
arguments that specify a command template. Then it will read lines
from stdin. Each line will be appended to the command template to
produce a command. The program will run the resulting command, one
command for each line of input.</p>
<p>Any questions?</p>
</blockquote>
<p>Default Claude is very chatty and I am going to omit most of its
chatty comments, which make me impatient.</p>
<p>The program it wrote it was not what I would have written, but it was
good enough. If I had just used it right off the bat, instead of
writing my own, it would have been enough, and it would have taken
somewhere between 2% and 10% as long to produce.</p>
<p>So the one-line summary of this article is: I should stop writing
simple command-line utilities, and just have Claude write them
instead, because Claude's are good enough, and definitely better than
yak-shaving.</p>
<p>Here's Claude's initial version:</p>
<pre><code> #!/usr/bin/env python3
import sys
import subprocess
import shlex
def main():
if len(sys.argv) < 2:
print("Usage: xar command [args...]", file=sys.stderr)
sys.exit(1)
command_template = sys.argv[1:]
for line in sys.stdin:
line = line.rstrip('\n')
full_command = command_template + [line]
try:
result = subprocess.run(full_command, check=True)
except subprocess.CalledProcessError as e:
print(f"Command failed with exit code {e.returncode}: {' '.join(full_command)}",
file=sys.stderr)
except FileNotFoundError:
print(f"Command not found: {full_command[0]}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
</code></pre>
<p>Claude's version had numerous comments, which I have omitted. I later
told it to stop putting in comments, which it did.</p>
<p>Claude's use of <code>check</code> here was not what I wanted, because that makes
<code>subprocess.run</code> raise an exception when the subcommand fails, and
then the exception has to be immediately caught and handled. My
original control flow had been simpler:</p>
<pre><code> res = subprocess.run(substitute_percents(command, filename), check=False)
return res.returncode == 0
…
if not run_command_with_filename(template, line):
ok = False
</code></pre>
<p>Claude's program pulled in <code>shlex</code> without noticing that it was
unused. But Claude did teach me about <code>str.rstrip('\n')</code> which I had
not known about before (or maybe had forgotten), so that was one small
win already.</p>
<h2>Argument parsing</h2>
<p>The next step was a big win. Python's library for command-line
argument handling is called <code>argparse</code> and it is <em>really</em> nice. If I
were still writing programs in Perl, I would implement a Perl version
of <code>argparse</code> because Perl has 29 available argument parsing libraries
and they are all loathsome. The one problem with <code>argparse</code> is I never
remember off the top of my head how to use it. I think the module is
called <code>argparse</code> but it provides a class called <code>Argparser</code> but I
often get these backward and try to use <code>argparser</code> and <code>Argparse</code>.</p>
<p>Instead of figuring it out every time I usually dig up some previous
Python program and then copy-paste the argument parser from there,
amending it to suit the purpose.</p>
<p>But this time I didn't have to do that. Instead, I just said to
Claude:</p>
<blockquote>
<p>This is good, now please add code at the top to handle argument
parsing with the standard <code>Argparse</code> library, even though there are
no options yet. </p>
</blockquote>
<p>Claude handed me back pretty much the same program, but with the
argument parser at the top:</p>
<pre><code> def parse_arguments():
"""Parse command-line arguments using argparse."""
parser = argparse.ArgumentParser(
description="A simplified version of xargs that appends each line from stdin to a command template.",
prog="xar"
)
# Store all positional arguments as the command to execute
parser.add_argument(
"command",
nargs="+",
help="The command template to execute for each input line"
)
return parser.parse_args()
</code></pre>
<p>Let's pause for a moment. Maybe you kids are unimpressed by this.
But if someone had sent this interaction back in time and showed it to
me even as late as five years ago, I would have been stunned. It
would have been completely science-fictional. The code that it
produced is not so impressive, maybe, but that I was able to get it in
response to an unstructured request in plain English is like seeing a
nuclear-powered jetpack in action.</p>
<h2>I make some changes</h2>
<p>It bothered me that the command-line arguments were being taken
implicitly from <code>sys.argv</code>; that makes the program hard to test. A
better way to do this is to have the list of command-line arguments be a
function to the <code>main</code> function, and then for the runtime to call
<code>main(sys.argv[1:])</code> instead of just <code>main()</code>. That way you can test
the program without having to fake up <code>sys.argv</code>. So I changed that,
and while I was making changes I got rid of Claude's comments and the
unnecessary exception handling. I pasted the result back at Claude,
saying:</p>
<blockquote>
<p>Good so far. I have adjusted your source code in several ways:</p>
<ol>
<li>I removed the comments.</li>
<li>I removed the exception handling for the subcommand. If one of the subcommands fails, that no longer terminates the main loop</li>
<li>Command-line arguments are passed to the main() function rather than being accessed directly</li>
</ol>
<p>Here is the code. Tell me if you see any problems or if you have any questions about it</p>
</blockquote>
<p>I had also sabotaged the code with at least one likely error.
Python's <code>subprocess.run</code> returns a “result” structure that includes
the subcommand's output and exit status, and I can never remember the
name of the component that records the exit status. I either look it
up or get it by trial-and-error. This time I didn't bother to either,
I just guessed that it was <code>result.exitcode</code>, which I handed off to
Claude even though I thought was probably wrong.</p>
<p>Claude's reply said:</p>
<blockquote>
<p>Here are a couple of issues I noticed:</p>
<p>There's an error in the error handling code - you're using <code>result.exitcode</code> but in the <code>subprocess.run()</code> function, the attribute is called <code>returncode</code> not <code>exitcode</code></p>
</blockquote>
<p>Awesome, now I don't have to remember that name of that component.</p>
<p>Claude continued:</p>
<blockquote>
<p>The <code>parse_arguments()</code> function definition is missing in the pasted code, but it would need to be updated to accept the <code>argv</code> parameter</p>
</blockquote>
<p>I had passed <code>sys.argv[1:]</code> to <code>main</code>, and then had <code>main</code> call <code>parse_arguments(argv)</code>.
But I hadn't changed <code>parse_arguments</code> to expect an argument list. (I
hadn't included it in my paste at all, in fact.) Claude was exactly
right, and continued with the corrected version:</p>
<pre><code> #!/usr/bin/env python3
import sys
import subprocess
import argparse
def parse_arguments(argv):
…
return parser.parse_args(argv)
def main(argv):
# Parse command-line arguments
args = parse_arguments(argv)
…
if __name__ == "__main__":
ok = main(sys.argv[1:])
exit(0 if ok else 1)
</code></pre>
<h2>Handling NUL-separated values</h2>
<p>I said</p>
<blockquote>
<p>It's great, thanks for catching those errors. Now let's add a <code>-0</code> command-line option that tells the program to split the input on "\0" characters instead of at line breaks.</p>
</blockquote>
<p>Recall that my handwritten <code>xar</code> did not do this yet; I had left that
feature for another day. Claude just added it for me, adding</p>
<pre><code> parser.add_argument(
"-0", "--null",
action="store_true",
help="Input items are terminated by a null character instead of by newline"
)
</code></pre>
<p>to the argument parser and changing the main loop to:</p>
<pre><code> # Split input based on the delimiter option
if args.null:
# Split on null characters
items = stdin_data.split('\0')
else:
# Split on newlines
items = stdin_data.splitlines()
for item in items:
if not item:
continue
…
</code></pre>
<p>I was tired of Claude's comments, so I said</p>
<blockquote>
<p>Give me the same thing, with no comments</p>
</blockquote>
<p>which it did, so I said</p>
<blockquote>
<p>From now on leave out the comments. I'm a Python expert and I don't need them. If there is something I don't understand I will ask you.</p>
</blockquote>
<p>Claude complied. If I were going to do use Claude again in the future
I would include that in the canned instructions that Claude is given
up front. Instead I will probably use a tool better-suited to
programming, and do whatever the analogous thing is.</p>
<h2>Template filling</h2>
<p>Now I told Claude to add the <code>%</code> feature:</p>
<blockquote>
<p>I changed <code>if not item</code> to <code>if item == ""</code>. Now let's make the following change:</p>
<ul>
<li>If the command template includes any <code>%</code> characters, each of these should be replaced with the input item.</li>
<li>Otherwise, if there were no <code>%</code> characters, the input item should be appended to the end of the command as in the current version</li>
</ul>
</blockquote>
<p>Claude did this. It used an explicit loop instead of the list
comprehension that I had used (and preferred), but it did do it correctly:</p>
<pre><code> for arg in command_template:
if '%' in arg:
has_placeholder = True
full_command.append(arg.replace('%', item))
else:
full_command.append(arg)
if not has_placeholder:
full_command.append(item)
</code></pre>
<p>Even without the list comprehension, I would have factored
out the common code:</p>
<pre><code> for arg in command_template:
if '%' in arg:
has_placeholder = True
full_command.append(arg.replace('%', item))
if not has_placeholder:
full_command.append(item)
</code></pre>
<p>But I am not going to complain, my code is simpler but is
doing unnecessary work.</p>
<p>Claude also took my hint to change <code>item == ""</code> even though I didn't
<em>explicitly</em> tell it to change that.</p>
<p>At this point the main loop of the <code>main</code> function was 15 lines long,
because Claude had stuck all the <code>%</code>-processing inline. So I said:</p>
<blockquote>
<p>Good, let's extract the command template processing into a subroutine.</p>
</blockquote>
<p>It did this right, understanding correctly what code I was referring
to and extracting it into a subroutine called
<code>process_command_template</code>. More science fiction: I can say "command
template processing" and it guesses what I had in mind!</p>
<p>This cut the main loop to 7 lines. That worked so well I tried it
again:</p>
<blockquote>
<p>Good, now let's extract the part of main that processes stdin into a subroutine that returns the <code>items</code> array</p>
</blockquote>
<p>It pulled the correct code into a function called <code>process_stdin</code>. It
did not make the novice mistake of passing the entire <code>args</code> structure
to this function. In the caller it had <code>process_stdin(args.null)</code> and
inside of <code>process_stdin</code> this parameter was named
<code>use_null_delimiter</code>,</p>
<h2>YAGNI?</h2>
<p>At this point I was satisfied but I thought I might as well ask if it
should do something else before we concluded:</p>
<blockquote>
<p>Can you think of any features I left out that would be useful enough
to warrant inclusion? Remember this program is supposed to be small
and easy to use, in contrast to the existing <code>xargs</code> which is very
complicated.</p>
</blockquote>
<p>Claude had four suggestions:</p>
<ol>
<li><p>A <code>-p</code> or <code>--parallel</code> option to run commands in parallel</p></li>
<li><p>A <code>-n</code> or <code>--max-args</code> option to specify the maximum number of items to pass per command</p></li>
<li><p>A simple <code>-v</code> or <code>--verbose</code> flag to show commands as they're executed</p></li>
<li><p>A way to replace the command's standard input with the item instead of adding it as an argument</p></li>
</ol>
<p>All reasonable suggestions, nothing stupid. (It also supplied code for #3,
which I had not asked for and did not want, but as I said before,
default Claude is very chatty.)</p>
<h2>Parallelization</h2>
<p>I didn't want any of these, and I knew that #2–4 would be easy to add if I
did want any of them later. But #1 was harder. I've done code like
this in the past, where the program has a worker pool and runs a new
process whenever the worker pool isn't at capacity. It's not even that
hard. In Perl you can play a cute trick and use something like</p>
<pre><code> $workers{spawn()} = 1 while delete $workers{wait()};
</code></pre>
<p>where the <code>workers</code> hash maps process IDs to dummy values. A child
exits, <code>wait()</code> awakens and returns the process ID of the completed
child, which is then deleted from the map, and the loop starts another
worker.</p>
<p>I wanted to see how Claude would do it, and the result was an even
bigger win than I had had previously, because Claude wrote this:</p>
<pre><code> with concurrent.futures.ProcessPoolExecutor(max_workers=args.parallel) as executor:
futures = [executor.submit(execute_command, cmd, args.verbose) for cmd in commands]
for future in concurrent.futures.as_completed(futures):
success = future.result()
if not success:
ok = False
</code></pre>
<p>What's so great about this? What's great is that I hadn't known about
<code>concurrent.futures</code> or <code>ProcessPoolExecutor</code>. And while I might have
suspected that something like them existed, I didn't know what they
were called. But now I do know about them.</p>
<p>If someone had asked me to write the <code>--parallel</code> option, I would have
had to have this conversation with myself:</p>
<blockquote>
<p>Python probably has something like this already. But how long will
it take me to track it down? And once I do, will the API
documentation be any good, or will it be spotty and incorrect? And
will there be only one module, or will there be three and I will
have to pick the right one? And having picked module F6, will I
find out an hour later that F6 is old and unmaintained and that
people will tell me “Oh, you should have used A1, it is the new
hotness, everyone knows that.”</p>
</blockquote>
<p>When I put all that uncertainty on a balance, and weigh it
against the known costs of doing it myself, which one wins?</p>
<p>The right choice is: I should do the research, find the good module (A1, not
F6), and figure out how to use it.</p>
<p>But one of my biggest weaknesses as a programmer is that I too often
make the wrong choice in this situation. I think “oh, I've done this
before, it will be quicker to just do it myself”, and then I do and it
is.</p>
<p>Let me repeat, <strong>it is quicker</strong> to do it myself. But that is still
the wrong choice.</p>
<p>Maybe the thing I wrote would be sooner or smaller or faster or more technically
suitable to the project than the canned module would have been. But it
would only have been more be technically suitable <em>today</em>. If it
needed a new feature in the future it might have to be changed by
someone who had never seen it before, whereas the canned module could well
already have the needed feature ready to go, already documented, and
perhaps already familiar to whoever had to make the change. My
bespoke version would certainly be unfamiliar to every <em>other</em>
programmer on the project — including perhaps myself six months later — so would be the wrong thing to use.</p>
<p>I'm really good at hacking this stuff up. Which is a <strong>problem</strong>. It
makes me want to hack stuff up, even when I shouldn't.</p>
<p>Claude tips the balance strongly toward the correct side, which is
that I should use the prepackaged module that someone else wrote and
not hack something up.</p>
<p>And now I know about <code>concurrent.futures.ProcessPoolExecutor</code>! The
world is full of manuals, how can I decide which ones I should read?
One way is to read them all, which used to work back when I was
younger, but now I have more responsibilities and I don't have time to
read the entire Python library reference including all the useless
bits I will never use. But here's Claude pointing out to me that this
is something I need to know about, now, today, and I should read <em>this</em> one. That is
valuable knowledge.</p>
<h2>Claude flubs</h2>
<p>While testing the program I did</p>
<pre><code> xar --parallel=2 bash -c '...'
</code></pre>
<p>which didn't work because the <code>argparse</code> library tried to parse the
<code>-c</code> as a flag to <code>xar</code>, when I wanted it to be a flag to <code>bash</code>.
This was my fault, and there is a standard way around it, which is
that I should have said</p>
<pre><code> xar --parallel=2 -- bash -c '...'
</code></pre>
<p>which tells <code>argparse</code> to drop the <code>--</code> and take everything after it
literally with no interpretation.</p>
<p>I thought I'd see what Claude had to say about this, and it dropped
the ball in a very LLM sort of way.</p>
<p>I asked:</p>
<blockquote>
<p>When I did <code>xar --parallel=2 bash -c '...'</code> it misunderstood the
arguments. It thought I was trying to pass <code>-c</code> to <code>xar</code> and failed
with unrecognized arguments. What should I do about that? </p>
</blockquote>
<p>Claude replied:</p>
<blockquote>
<p>There are two main approaches to handle this:</p>
<ol>
<li>Use the <code>--</code> separator to indicate the end of arguments for xar: …</li>
<li>Modify your code to handle this case specifically by looking for a command terminator in the argument parser.</li>
</ol>
<p>Here's how you could modify your code to implement the first
approach:</p>
</blockquote>
<p>And then it followed with the same, <em>unmodified</em> code…</p>
<p>Which was <em>correct</em>, because <code>--</code> is already handled by <code>argparse</code>, so
no change was actually required to implement the first approach! But
Claude's characterization of it as a <em>modification</em> was misleading,
wrong, and potentially very confusing.</p>
<p>I said:</p>
<blockquote>
<p>I don't see what the difference is in that argument parsing code as compared to what I had before.</p>
</blockquote>
<p>and Claude got into the LLM tail-chasing mode where it got more and
more confused:</p>
<blockquote>
<p>You're right, I apologize for the confusion. There's no difference
in the argument parsing code I provided compared to what you had
before. The issue is with how argparse handles arguments by
default. </p>
<p>To fix the issue with <code>xar --parallel=2 bash -c '...',</code> you need to
actually implement support for the <code>--</code> separator in your code… </p>
<pre><code> # Find the index of '--' if it exists
try:
separator_index = argv.index('--')
xar_args = argv[:separator_index]
command_args = argv[separator_index+1:]
except ValueError: …
</code></pre>
</blockquote>
<p>Ugh, all wrong in the worst way. The code probably works, but it is
completely unnecessary. Claude's claim that “you need to actually
implement support for the <code>--</code> separator” is flat wrong. I pointed
this out and Claude got more confused. Oh well, nobody is perfect!</p>
<h2>Lessons learned</h2>
<p>A long time ago, when syntax-coloring editors were still new, I tried
one and didn't like it, then tried again a few years later and
discovered that I liked it better than I had before, and not for the
reasons that anyone had predicted or that I would have been able to
predict.
(<a href="https://blog.plover.com/prog/syntax-coloring-1998.html">I wrote an article about the surprising reasons to use the syntax coloring</a>.)</p>
<p>This time also. As usual, an actual experiment produced unexpected
results, because the world is complicated and interesting. Some of
the results were unsurprising, but some were not anything I would have
thought of beforehand.</p>
<h3>Claude's code is good enough, but it is not a magic oracle</h3>
<p>Getting Claude to write most of the code was a lot faster and easier
than writing it myself. This is good! But I was dangerously tempted
to just take Claude's code at face value instead of checking it
carefully. I quickly got used to flying along at great speed, and it
was tough to force myself to slow down and be methodical, looking over
everything as carefully as I would if Claude were a real junior
programmer. It would be easy for me to lapse into bad habits,
especially if I were tired or ill. I will have to be wary.</p>
<p>Fortunately there is already a part of my brain trained to deal with
bright kids who lack experience, and I think perhaps that part of my brain
will be able to deal effectively with Claude.</p>
<p>I did not notice any mistakes on Claude's part — at least this time.</p>
<p>At one point my testing turned up what appeared to be a bug, but it
was not. The testing was still time well-spent.</p>
<h3>Claude remembers the manual better than I do</h3>
<p>Having Claude remember stuff for me, instead of rummaging the
manual, is great. Having Claude stub out an argument parser,
instead of copying one from somewhere else, was pure win.</p>
<p>Partway along I was writing a test script and I wanted to use that
Bash flag that tells Bash to quit early if any of the subcommands
fails. I can never remember what that flag is called. Normally I
would have hunted for it in one of my own shell scripts, or groveled
over the 378 options in the <code>bash</code> manual. This time I just asked in
plain English “What's the <code>bash</code> option that tells the script to abort
if a command fails?” Claude told me, and we went back to what we were
doing.</p>
<h3>Claude can talk about code with me, at least small pieces</h3>
<p>Claude easily does simple refactors. At least at this scale, it got
them right. I was not expecting this to work as well as it did.</p>
<p>When I told Claude to stop commenting every line, it did. I
wonder, if I had told it to use <code>if not expr</code> only for Boolean
expressions, would it have complied? Perhaps, at least for a
while.</p>
<p>When Claude wrote code I wasn't sure about, I asked it what it was
doing and at least once it explained correctly. Claude had written</p>
<pre><code> parser.add_argument(
"-p", "--parallel",
nargs="?",
const=5,
type=int,
default=1,
help="Run up to N commands in parallel (default: 5)"
)
</code></pre>
<p>Wait, I said, I know what the <code>const=5</code> is doing, that's so that if
you have <code>--parallel</code> with no number it defaults to 5. But what is
the <code>--default</code> doing here? I just asked Claude and it told me:
that's used if there is no <code>--parallel</code> flag at all.</p>
<p>This was much easier than it would have been for me to pick over
the <code>argparse</code> manual to figure out how to do this in the first
place.</p>
<h2>More thoughts</h2>
<p>On a different project, Claude might have done much worse. It might
have given wrong explanations, or written wrong code. I think that's
okay though. When I work with human programmers, they give wrong
explanations and write wrong code all the time. I'm used to it.</p>
<p>I don't know how well it will work for larger systems. Possibly pretty
well <em>if</em> I can keep the project sufficiently modular that it doesn't get
confused about cross-module interactions. But if the criticism is
“that LLM stuff doesn't work unless you keep the code extremely
modular” that's not much of a criticism. We all need more
encouragement to keep the code modular.</p>
<p>Programmers often write closely-coupled modules knowing that it is bad
and it will cause maintenance headaches down the line, knowing that the
problems will most likely be someone else's to deal with. But what if
writing closely-coupled modules had an immediate cost today, the cost
being that the LLM would be less helpful and more likely to mess up
today's code? Maybe programmers would be more careful about letting
that happen!</p>
<h2>Will my programming skill atrophy?</h2>
<p>Folks at Recurse Center were discussing this question.</p>
<p>I don't think it will. It will only atrophy if I let it. And I have a
pretty good track record of not letting it. The essence of
engineering is to pay attention to what I am doing and why, to try to
produce a solid product that satisifes complex constraints, to try
to spot problems and correct them. I am not going to stop doing
this. Perhaps the problems will be different ones than they were
before. That is all right.</p>
<p>Starting decades ago I have repeatedly told people</p>
<blockquote>
<p>You cannot just paste code with no understanding of
what is going on and expect it to work.</p>
</blockquote>
<p>That was true then without Claude and it is true now with Claude. Why
would I change my mind about this? How could Claude change it?</p>
<p>Will I lose anything from having Claude write that complex
<code>parser.add_argument</code> call for me? Perhaps if I had figured it out
on my own, on future occasions I would have remembered the <code>const=5</code> and <code>default=1</code>
specifications and how they interacted. Perhaps.</p>
<p>But I suspect that I <em>have</em> figured it out on my own in the past, more
than once, and it <em>didn't</em> stick. I am happy with how it went this time.
After I got Claude's explanation, I checked its claimed behavior pretty
carefully with a stub program, as if I had been reviewing a
colleague's code that I wasn't sure about.</p>
<p>The biggest win Claude gave me was that I didn't know about this
<code>ProcessPoolExecutor</code> thing before, and now I do. That is going to
make me a <em>better</em> programmer. Now I know something about useful that
I didn't know before, and I have a pointer to documentation I know I
should study.</p>
<p>My skill at writing ad-hoc process pool managers might atrophy, but if
it does, that is <strong>good</strong>. I have already written too many ad-hoc
process pool managers. It was a bad habit, I should have stopped long
ago, and this will help me stop.</p>
<h2>Conclusion</h2>
<p>This works.</p>
<p>Perfectly? No, it's technology, technology never works perfectly.
Have you ever <em>used</em> a computer?</p>
<p>Will it introduce new problems? Probably, it's new technology, and
new technology always introduces new problems.</p>
<p>But is it better than what we had before? Definitely.</p>
<p>I still see some programmers turning up their noses at this technology
as if they were sure it was a silly fad that would burn itself out
once people came to their senses and saw what a terrible idea it was.</p>
<p>I think that is not going to happen, and those nose-turning-up people,
like the people who pointed out all the drawbacks and unknown-unknowns
of automobiles as compared to horse-drawn wagons, are going to look
increasingly foolish.</p>
<p>Because it works.</p>
-
A puzzle about balancing test tubes in a centrifuge
https://blog.plover.com/2025/05/01#centrifuge
<p><img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdef%5cnk%231%232%7b%5cleft%5clangle%7b%231%20%5catop%20%232%7d%5cright%5crangle%7d%0a%5cdef%5cdd%231%7b%5cnk%7b12%7d%7b%231%7d%7d%0a%24"></p>
<p>Suppose a centrifuge has <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%24"> slots, arranged in a circle around the
center, and we have <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24"> test tubes we wish to place into the slots.
If the tubes are not arranged symmetrically around the center, the
centrifuge will explode.</p>
<p>(By "arranged symmetrically around the center, I mean that if the
center is at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%280%2c0%29%24">, then the sum of the positions of the tubes
must also be at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%280%2c0%29%24">.)</p>
<p>Let's consider the example of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%3d12%24">. Clearly we can arrange <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%242%24">,
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%243%24">, <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%244%24">, or <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%246%24"> tubes symmetrically:</p>
<div style="margin: auto;">
<img src="https://pic.blog.plover.com/math/centrifuge/n12k2.svg" style="width: 20%; margin: 2%;"
alt="twelve small circles arranged around a central point, like a
clock; circles 5 and 11 are filled in" />
<img src="https://pic.blog.plover.com/math/centrifuge/n12k3.svg" style="width: 20%; margin: 2%;"
alt="cirles 1, 5, and 9 are filled in" />
<img src="https://pic.blog.plover.com/math/centrifuge/n12k4.svg" style="width: 20%; margin: 2%;"
alt="circles 0, 3, 6, and 9 are filled" />
<img src="https://pic.blog.plover.com/math/centrifuge/n12k6.svg" style="width: 20%; margin: 2%;"
alt="circles 1, 3, 5, 7, 9, and 11 are filled" />
</div>
<p>Equally clearly
we can't arrange only <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%24">. Also it's easy to see we can do <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24"> tubes if
and only if we can also do <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%2dk%24"> tubes, which rules out <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%3d12%2c%0ak%3d11%24">.</p>
<p>From now on I will write <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%20nk%24"> to mean the problem of balancing
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24"> tubes in a centrifuge with <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%24"> slots. So <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd%202%2c%20%5cdd%203%2c%20%5cdd%0a4%2c%20%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd%206%24"> are possible, and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd%201%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd%7b11%7d%24"> are
not. And <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%20nk%24"> is solvable if and only if <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%20n%7bn%2dk%7d%24"> is.</p>
<p>It's perhaps a little surprising that <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd7%24"> is possible.
If you just ask this to someone out of nowhere they might
have a happy inspiration: “Oh, I'll just combine the solutions for
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd3%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd4%24">, easy.” But that doesn't work because two groups
of the form <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%243i%2bj%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%244i%2bj%24"> always overlap.</p>
<p>For example, if your group of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%244%24"> is the
slots <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%2c%203%2c%206%2c%209%24"> then you can't also have your group of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%243%24"> be
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%2c%205%2c%209%24">, because slot <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%249%24"> already has a tube in it. </p>
<div style="margin: auto;">
<img src="https://pic.blog.plover.com/math/centrifuge/n12k7x.svg" style="width: 30%;"
alt="The k=3 and k=4 diagrams from before, superimposed; there is a
small explosion symbol at 9 o'clock where they interfere">
</div>
<p>The
other balanced groups of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%243%24"> are blocked in the same way. You
cannot solve the puzzle with <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%3d3%2b4%24">; you have to do <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%3d3%2b2%2b2%24"> as
below left.
The best way to approach this is to do <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd5%24">, as below right.
This is easy,
since the triangle only blocks three of the six symmetric pairs.
Then you replace the holes with tubes and the tubes with holes to
turn <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd5%24"> into <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd%7b12%2d5%7d%3d%5cdd7%24">.</p>
<div style="margin: auto;">
<img src="https://pic.blog.plover.com/math/centrifuge/n12k7.svg" style="width: 25%; margin: 5%;"
alt="a triangle filling slots 1, 5, and 9, plus a pair at 0, 6 and
another pair at 2, 8" />
<img src="https://pic.blog.plover.com/math/centrifuge/n12k5.svg" style="width: 25%; margin: 5%;"
alt="the opposite of the previous diagram, consisting of a triangle
filling slots 3, 7, 11 and a pair at 4, 10" />
</div>
<p>Given <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24">, how can we decide whether the centrifuge can be
safely packed?</p>
<p>Clearly you can solve <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%20nk%24"> when <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%24"> is a multiple of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%3e1%24">, but the example
of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd5%24"> (or <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd7%24">) shows this isn't a necessary condition.</p>
<p>A generalization of this is that <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%20nk%24"> is always solvable
if <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cgcd%28n%2ck%29%20%3e%201%24"> since you can easily
balance <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24g%20%3d%20%5cgcd%28n%2c%20k%29%24"> tubes at positions <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%2c%20%5cfrac%20ng%2c%20%5cfrac%7b2n%7dg%2c%20%5cdots%2c%0a%5cfrac%20%7b%28g%2d1%29n%7dg%24">, then do another <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24g%24"> tubes one position over, and
so on. For example, to do <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd8%24"> you just put first four tubes
in slots <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%2c%203%2c%206%2c%209%24"> and the next four one position over, in slots
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%2c%204%2c%207%2c%2010%24">.</p>
<div style="margin: auto;">
<img src="https://pic.blog.plover.com/math/centrifuge/n12k8.svg" style="width: 30%;"
alt="Two squares this time, as described in the previous paragraph">
</div>
<p>An interesting counterexample is that the strategy for <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cdd7%24">,
where we did <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%3d3%2b2%2b2%24">, cannot be extended to <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%7b14%7d9%24">. One
would want to do <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%3d7%2b2%24">, but there is no way to arrange the tubes
so that the group of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%242%24"> doesn't conflict with the group of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%24">,
which blocks one slot from every pair.</p>
<p>But we can see that this must be true without even considering the
geometry. <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%7b14%7d9%24"> is the reverse of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%7b14%7d%7b14%2d9%7d%20%3d%20%5cnk%7b14%7d5%24">, which
impossible: the only nontrivial divisors of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%3d14%24"> are <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%242%24"> and
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%24">, so <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24"> must be a sum of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%242%24">s and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%24">s, and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%245%24"> is not.</p>
<p>You can't fit <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%3d3%2b5%3d8%24"> tubes when <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%3d15%24">, but again the reason is
a bit tricky. When I looked at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%248%24"> directly, I did a case analysis
to make sure that the <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%243%24">-group and the <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%245%24">-group would always
conflict. But again there was an easier was to see this: <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%248%3d15%2d7%24"> and
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%24"> clearly won't work, as <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%247%24"> is not a sum of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%243%24">s and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%245%24">s.
I wonder if there's an example where both <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%2dk%24"> are not obvious?</p>
<p>For <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%3d20%24">, every <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%24"> works except <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%3d3%2c17%24"> and the always-impossible <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24k%3d1%2c19%24">.</p>
<p>What's the answer in general? I don't know.</p>
<h2>Addenda</h2>
<h3>20250502</h3>
<p>Now I am amusing myself thinking about the perversity of a centrifuge
with a prime number of slots, say <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%2413%24">. If you use it at all, you must
fill every slot. I hope you like explosions!</p>
<p>While I did not explode any centrifuges in university chemistry, I did
once explode an expensive <a href="https://en.wikipedia.org/wiki/Liebig_condenser">Liebig condenser</a>.</p>
<p><a href="https://pic.blog.plover.com/math/centrifuge/condenser.jpg"><img
src="https://pic.blog.plover.com/math/centrifuge/condenser-th.jpg" class="center" alt="A chemistry lab
apparatus. In the middle thje the Liebig condenser, a pair of long
concentric glass tubes, one inside the other, supported by a metal
ring stand. At left a heater heats a flash whose top is connected to
the upper end of the inner tube of the condenser. The condensate
collects in a flask at right. Two rubber tubes connect to the top and
bottom of the outer tube, carrying water through it."></a></p>
<p><span style="font-size: x-small">Condenser setup
by <a
href="https://commons.wikimedia.org/w/index.php?title=User:Mario_Link">Mario
Link</a> from <a
href="https://www.flickr.com/photos/24049265@N00/162821856">an
original image</a> by <a
href="https://www.flickr.com/photos/arlen/">Arlen on Flickr</a>.
Licensed <a
href="https://creativecommons.org/licenses/by/2.0/deed.en">cc-by-2.0</a>,
provided via <a
href="https://commons.wikimedia.org/wiki/File:Distillation_2-3.jpg">Wikimedia Commons</a>.</p>
<h3>20250503</h3>
<ul>
<li><p>Michael Lugo informs me that
<a href="https://mattbaker.blog/2018/06/25/the-balanced-centrifuge-problem/">a complete solution may be found on Matt Baker's math blog</a>. I
have not yet looked at this myself.</p></li>
<li><p><a href="https://mathstodon.xyz/@oantolin/114434195433254052">Omar Antolín points out an important consideration I missed</a>:
it may be necessary
to <em>subtract</em> polygons. Consider <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%7b30%7d6%24">. This is obviously
possible since <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%246%5cmid%2030%24">. But there is a more interesting
solution. We can add the pentagon <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5c%7b0%2c%206%2c%2012%2c%2018%2c%2024%5c%7d%24"> to the
digons <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5c%7b5%2c%2020%5c%7d%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5c%7b10%2c%2025%5c%7d%24"> to obtain the solution
$${0,5,6,10,12,18, 20, 24, 25}.$$</p>
<p><img src="https://pic.blog.plover.com/math/centrifuge/n30k9.svg" style="width: 40%;" class="center"
alt=""/> Then from this we can <em>subtract</em> the triangle <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5c%7b0%2c%2010%2c%0a%20%2020%5c%7d%24"> to obtain $${5, 6, 12, 18, 24, 25},$$ a solution to
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5cnk%7b30%7d6%24"> which is <em>not</em> a sum of regular polygons:</p>
<p><img src="https://pic.blog.plover.com/math/centrifuge/n30k6b.svg" style="width: 40%;" class="center"
alt=""/></p></li>
<li><p>Thanks to Dave Long for pointing out a small but significant error,
which I have corrected.</p></li>
</ul>
<h3>20250505</h3>
<ul>
<li>Robin Houston points out this video,
<a href="https://www.youtube.com/watch?v=7DHE8RnsCQ8">The centrifuge Problem</a>
with Holly Krieger, on <a href="https://www.youtube.com/@numberphile">the Numberphile channel</a>.</li>
</ul>
-
Proof by insufficient information
https://blog.plover.com/2025/04/30#proof-by-symmetry
<p>Content warning: rambly</p>
<p>Given the coordinates of the three vertices of a triangle, can we find
the area? Yes. If by no other method, we can use the Pythagorean
theorem to find the lengths of the edges, and then
<a href="https://en.wikipedia.org/wiki/Heron%27s_formula">Heron's formula</a> to compute the area from
that.</p>
<p>Now, given the coordinates of the four vertices of a quadrilateral,
can we find the area? And the answer is, no, there is no method to do
that, <em>because there is not enough information</em>:</p>
<p><img class="center" border=0 src="https://pic.blog.plover.com/math/proof-by-symmetry/q1.svg" alt="three points
arranged in an irregular triangle, with one in the middle. Four of the six
possible edges are drawn in, definining a quadrilateral" />
<img class="center" border=0 src="https://pic.blog.plover.com/math/proof-by-symmetry/q2.svg" alt="the same
four points, but with three different edges drawn in to define a
different quadrilateral with the same vertices" />
<img class="center" border=0 src="https://pic.blog.plover.com/math/proof-by-symmetry/q3.svg" alt="a third
quadrilateral with the same vertices as the first two" /></p>
<p>These three quadrilaterals have the same vertices, but different
areas. Just knowing the vertices is not enough; you also need their order.</p>
<p>I suppose one could abstract this: Let <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24f%24"> be the function that maps
the set of vertices to the area of the quadrilateral. Can we
calculate values of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24f%24">? No, because there is no such <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24f%24">, it is
not well-defined.</p>
<p>Put that way it seems less interesting. It's just another example of
the principle that, just because you put together a plausible sounding
description of some object, you cannot infer that such an object must
exist. One of the all-time pop hits here is:</p>
<blockquote>
<p>Let <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%ce%b5%24"> be the smallest [real / rational] number strictly greater than <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%24">…</p>
</blockquote>
<p>which appears on Math SE quite frequently. Another one I remember is
someone who asked about
<a href="https://math.stackexchange.com/q/838561/25554">the volume of a polyhedron with exactly five faces, all triangles</a>. This
is a fallacy at the ontological level, not the mathematical
level, so when it comes up I try to demonstrate it with a
nonmathematical counterexample, usually something like “the largest
purple hat in my closet” or perhaps “the current Crown Prince of the
Ottoman Empire”. The latter is less good because it relies on the
other person to know obscure stuff about the Ottoman Empire, whatever
that is.</p>
<p>This is also unfortunately also the error in Anselm's so-called
“ontological proof of God”. A philosophically-minded friend of mine
once remarked that being known for the discovery of the ontological
proof of God is like being known for the discovery that you can wipe
your ass with your hand.</p>
<p>Anyway, I'm digressing. The interesting part of the quadrilateral
thing, to me, is not so much that <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24f%24"> doesn't exist, but the specific
reasoning that demonstrates that it <em>can't</em> exist. I think there are
more examples of this proof strategy, where we prove nonexistence
by showing there is not enough information for the thing to exist, but
I haven't thought about it enough to come up with one.</p>
<p>There is a proof, the so-called
<a href="https://math.stackexchange.com/a/1313546/25554">“information-theoretic proof”</a>,
that a comparison sorting algorithm takes at least <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24O%28n%5clog%20n%29%24"> time, based
on comparing the amount of information gathered from the comparisons
(one bit each) with that required to distinguish all <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%21%20%24"> possible
permutations (<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%5clog_2%20n%21%20%5cge%20n%5clog_2%20n%24"> bits total). I'm not sure
that's what I'm looking for here. But I'm also not sure it isn't, or
why I feel it might be different.</p>
<h2>Addenda</h2>
<h3>20250430</h3>
<p>Carl Muckenhoupt suggests that logical independence proofs are of the
same sort. He says, for example:</p>
<blockquote>
<p>Is there a way to prove the parallel postulate from Euclid's other
axioms? No, there is not enough information. Here are two geometric
models that produce different results.</p>
</blockquote>
<p>This is just the sort of thing I was looking for.</p>
<h3>20250503</h3>
<p>Rik Signes has allowed me to reveal that he was the source of the
memorable disparagement of Anselm's dumbass argument.</p>
<!-- I had previously gotten blanket permission from him in connection
with still-unpublished article math/logic/properties.html -->
-
Willie Singletary will you please go now?
https://blog.plover.com/2025/04/26#Singletary
<p>(Previously: <a href="https://blog.plover.com/politics/blago.html">[1]</a> <a href="https://blog.plover.com/law/fortunato-n-perri.html">[2]</a>)</p>
<p>Welcome to Philadelphia! We have a lot of political corruption here.
<a href="https://blog.plover.com/law/fortunato-n-perri.html">I recently wrote about the unusually corrupt Philadelphia Traffic Court</a>,
where four of the judges went to the federal pokey, and the state
decided there was no way to clean it up, they had to step on it like a
cockroach. I ended by saying:</p>
<blockquote>
<p>One of those traffic court judges was Willie Singletary, who I've
been planning to write about since 2019. But he is a hard worker who
deserves better than to be stuck in an epilogue, so I'll try to get
to him later this month.</p>
</blockquote>
<p>This is that article from 2019, come to fruit at last. It was
originally inspired by this notice that appeared at my polling place on
election day that year:</p>
<p><a href="https://pic.blog.plover.com/politics/Singletary/singletary.jpg"><span style="font-size: small;">(Click for uncropped version)<span><br>
<img class="center" border=0 src="https://pic.blog.plover.com/politics/Singletary/singletary-cropped-th.jpg" alt="
Cropped version of a notice posted in a Philadelphia polling place,
text as below”" /></a></p>
<blockquote>
<p><strong>VOTES FOR THIS CANDIDATE WILL NOT BE COUNTED</strong></p>
<p><strong>DEAR VOTERS</strong>:</p>
<p>Willie Singletary, candidate for Democratic Council At-Large,
has been removed from the Primary Ballot by Court Order.
Although his name appears on the ballot, votes for this
candidate will not be counted because he was convicted of two
Class E felonies by the United States District Court for the
Eastern District of Pennsylvania, which bars his candidacy under
Article 2, Section 7 of the Pennsylvania Constitution.</p>
</blockquote>
<p>That's because Singletary had been one of those traffic court judges.
In 2014 he had been convicted of lying to the FBI in connection with
that case, and was sentenced to 20 months in federal prison; I think
he actually served 12.</p>
<p>That didn't stop Willie from trying to run for City Council, though,
and the challenge to his candidacy didn't wrap up before the ballots
were printed, so they had to post these notices.</p>
<p>Even before the bribery scandal and the federal conviction,
Singletary had already lost his Traffic Court job when it transpired
that he had showed dick pics to a Traffic Court cashier.</p>
<p>Before that, when he was campaigning for the Traffic Court job, he was
caught on video promising to give favorable treatment to campaign donors.</p>
<!-- At that time he had also had his driver's license suspended
over $11,000 in unpaid traffic fines, but let's not belabor the
point. -->
<p>But Willie's enterprise and go-get-it attitude means he can't be kept
down for long. Willie rises to all challenges! He is now enjoying a
$90,000 annual salary as a Deputy Director of Community Partnerships
in the administration of Philadelphia Mayor Cherelle Parker. Parker's
spokesperson says
<a href="https://www.inquirer.com/news/willie-singletary-leslie-acosta-movita-johnson-harrell-corruption-conviction-philadelphia-20241119.html">"The Parker administration supports every person’s right to a second chance in society.”</a></p>
<p>I think he might be on his fourth or fifth chance by now, but who's
counting? Let it never be said that Willie Singletary was a quitter.</p>
<p>Lorrie once made a remark that will live in my memory forever, about
the "West Philadelphia local politics-to-prison pipeline”. Mayor
Parker is such a visionary that she has been able to establish a
second pipeline in the opposite direction!</p>
<h3>Addendum 20250501</h3>
<p>I don't know how this happened, but when I committed the final version
of this article a few days ago, the commit message that my fingers
typed was:</p>
<pre><code>Date: Sat Apr 26 14:24:19 2025 -0400
Willie Wingletsray finally ready to go
</code></pre>
<p>And now, because Git, it's written in stone.</p>
-
How our toy octopuses got revenge on a Philadelphia traffic court judge
https://blog.plover.com/2025/04/24#fortunato-n-perri
<p>[ Content warning: possibly amusing, but silly and pointless ]</p>
<p>My wife Lorrie wrote this on 31 January 2013:</p>
<blockquote>
<p>I got an e-mail from Husband titled, "The mills of Fenchurch grind slow,
but they grind exceeding small." This silliness, which is off-the-charts
silly, is going to require explanation.</p>
<p>Fenchurch is a small blue octopus made of polyester fiberfill. He was the
first one I ever bought, starting our family's octopus craze, and I gave
him to Husband in 1994. He is extremely shy and introverted. He hates
conflict and attention. He's a sensitive and very artistic soul. His
favorite food is crab cakes, followed closely by shrimp. (We have made up
favorite foods, professions, hobbies, and a zillion scenarios for all of
our stuffed animals.)</p>
</blockquote>
<p>In our house it was well-established canon that Fenchurch's
favorite food was crab cakes. I had even included him as an example
in some of my conference talks:</p>
<pre><code> my $fenchurch = Octopus->new({
arms => 8,
hearts => 3,
favorite_food => "crab cakes"
});
</code></pre>
<blockquote>
<p>He has a ladylove named Junko whom he takes on buggy
rides on fine days. When Husband is feeling very creative and vulnerable,
he identifies with Fenchurch.</p>
<p>Anyway, one time Husband got a traffic ticket and this Traffic Court
judge named Fortunato N. Perri was unbelievably mocking to him at
his hearing. Good thing Husband has the thick skin of a native
Manhattanite. … It was so awful that Husband and I remember bits
of it more than a decade later.</p>
</blockquote>
<p>I came before Fortunato N. Perri in, I think, 1996. I had been
involved in a very low-speed collision with someone, and I was
ticketed because the proof of insurance in my glove box was expired.
Rather than paying the fine, I appeared in traffic court to plead not
guilty.</p>
<p>It was clear that Perri was not happy with his job as a traffic
court judge. He had to listen to hundreds of people making the same
lame excuses day after day. “I didn't see the stop sign.” “The sun
was in my eyes.” “I thought the U-turn was legal.” I can't blame
Perri for growing tired of this. But I can blame him for the way
he handled it, which was to mock and humiliate the people who came
before him.</p>
<p>“Where are you from?”</p>
<p>“Ohio.”</p>
<p>“Do they have stop signs in Ohio?”</p>
<p>“Uh, yes.”</p>
<p>“Do you know what they look like?”</p>
<p>“Yes.”</p>
<p>“Do they look like the stop signs we have here?”</p>
<p>“Yes.”</p>
<p>“Then how come you didn't see the stop sign? You say you know what a
stop sign looks like but then you didn't stop. I'm fining you $100.
You're dismissed.”</p>
<p>He tried to hassle me also, but I kept my cool, and since I wasn't
actually in violation of the law he couldn't do anything to me. He
did try to ridicule my earring.</p>
<p>“What does that thing mean?”</p>
<p>“It doesn't mean anything, it's just an earring.”</p>
<p>“Is that what everyone is doing now?”</p>
<p>“I don't know what everyone is doing.”</p>
<p>“How long ago did you get it?”</p>
<p>“Thirteen years.”</p>
<p>“Huh. … Well, you did have insurance, so I'm dismissing your ticket.
You can go.”</p>
<p>I'm still wearing that earring today, Fortunato. By the way,
Fortunato, the law is supposed to be calm and impartial, showing favor
to no one.</p>
<p>Fortunato didn't just mock and humiliate the unfortunate citizens who
came before him. He also abused his own clerks. One of them was doing
her job, stapling together court papers on the desk in front of the
bench, and he harangued her for doing it too noisily. “God, you might
as well bring in a hammer and nails and start hammering up here, bang
bang bang!”</p>
<p>I once went back to traffic court just to observe, but he wasn't in
that day. Instead I saw how a couple of other, less obnoxious judges
ran things.</p>
<p>Lorrie continues:</p>
<blockquote>
<p>Husband has been following news about this judge (now
retired) and his family ever since, and periodically he gives me updates.</p>
</blockquote>
<p>(His son, Fortunato N. Perri Jr., is a local civil litigation attorney
of some prominence. As far as I know there is nothing wrong with
Perri Jr.)</p>
<blockquote>
<p>And we made up a story that Fenchurch was traumatized by this guy after
being ticketed for parking in a No Buggy zone.</p>
<p>So today, he was <a href="https://www.philly.com/philly/news/breaking/20130131_Traffic_Court_judges_begin_surrendering_to_FBI.html">charged with corruption after a three-year FBI probe</a>.
<a href="https://www.inquirer.com/philly/news/homepage/20100819_FBI_and_IRS_agents_raid_three_prominent_Philadelphia_political_players.html">The FBI even raided his house</a></p>
<p>I understood everything when I read that Perri accepted graft in many
forms, including <strong>shrimp</strong> and <strong>crab cakes</strong>.</p>
<p>OMG. No wonder my little blue octopus was wroth. No wonder he swore
revenge. This crooked thief was interfering with his food supply!</p>
</blockquote>
<p>Lorrie wrote a followup the next day:</p>
<blockquote>
<p>I confess Husband and I spent about 15 minutes last night savoring details
about Fortunato N. Perri's FBI bust. Apparently, <em>even he</em> had a twinge of
conscience at the sheer <em>quantity</em> of <strong><em>SHRIMP</em></strong> and <strong><em>CRAB CAKES</em></strong> he got from
this one strip club owner in return for fixing tickets. (Husband noted
that he managed to get over his qualms.)</p>
<p>Husband said Perri hadn't been <em>too</em> mean to him, but Husband still
feels bad about the way Perri screamed at his hapless courtroom
assistant, who was innocently doing her job stapling papers until
Perri stopped proceedings to holler that she was making so much
noise, she may as well be using a hammer.</p>
<p>Fenchurch and his ladylove Junko, who specialize in avant garde performance
art, greeted Husband last night with their newest creation, called
"Schadenfreude." It mostly involved wild tentacle waving and uninhibited
cackling. Then they declared it to be the best day of their entire lives
and stayed up half the night partying.</p>
<p><a href="https://pic.blog.plover.com/law/fortunato-n-perri/jf.jpg"><img class="center" src="https://pic.blog.plover.com/law/fortunato-n-perri/jf-th.jpg" alt="Fenchurch, at left, is a bright blue toy octopus with a round body, beady black eyes rimmed in an olive gold color, and no visible mouth. His body and arms are covered with shirt but shaggy plush. Junko, who sits at right, her eight arms entwined with his, is almost exactly the same, except that her plush is hot pink. They are resting on a quilt, presumably on a bed somewhere, and looking directly into the camera.">
</a></p>
</blockquote>
<h3>Epilogues</h3>
<ul>
<li><p>Later that year, the notoriously corrupt Traffic Court was
abolished, its functions transferred to regular Philadelphia
Municipal Court.</p></li>
<li><p>In late 2014, four of Perri's Traffic Court colleagues were
convicted of federal crimes.
<a href="https://www.justice.gov/usao-edpa/pr/former-traffic-court-judge-sentenced-lying-fbi">They received prison sentences of 18 to 20 months</a>.</p></li>
<li><p>Fortunato Perri himself, by then 78 years old and in poor health,
pled guilty, and
<a href="https://www.philly.com/philly/news/20150725_Fortunato_Perri_Sr__gets_probation_in_traffic_court_scandal.html">was sentenced to two years of probation</a>.</p></li>
<li><p>The folks who supplied the traffic tickets and the seafood bribes
were also charged. They tried to argue that they hadn't defrauded
the City of Philadelphia because the people they paid Perri to let
off the hook hadn't been found guilty, and would only have owed
fines <em>if</em> they had been found guilty.</p>
<p>The judges in their appeal were not impressed with this argument.
See <a href="https://caselaw.findlaw.com/court/us-3rd-circuit/1976350.html">United States v. Hird et al.</a>.</p></li>
<li><p>One of those traffic court judges was Willie Singletary, who I've
been planning to write about since 2019. But he is a hard worker
who deserves better than to be stuck in an epilogue, so I'll try to
get to him later this month.</p>
<p>(Update 20250426: <a href="https://blog.plover.com/politics/Singletary.html">Willie Singletary, ladies and gentlemen</a>!)</p></li>
</ul>
<!-- https://www.courierpostonline.com/article/20130131/NEWS/301310051/Feds-Fix-Philly-s-traffic-court?nclick_check=1 -->
-
Does someone really have to do the dirty jobs?
https://blog.plover.com/2025/03/28#dirty-jobs
<p>Doing the laundry used to be backbreaking toil. Haul the water, chop
the wood, light the fire, heat the water, and now you are ready to
begin the really tough part of the work. The old saying goes "Wash on
Monday", because Monday is the day after your day of rest, and
otherwise you won't have the strength to do the washing.</p>
<p>And the saying continues: “Iron on Tuesday, mend on Wednesday”.
Routine management of clothing takes half of the six-day work week.</p>
<p>For this reason, washing is the work of last resort for the poorest
and most marginal people. Widows are washerwomen.
<a href="https://en.wikipedia.org/wiki/Magdalene_laundry">Prisons are laundries</a>.
<a href="https://en.wikipedia.org/wiki/Yick_Wo_v%2e_Hopkins">Chinese immigrants run laundries</a>. Anyone
with enough money to outsource their laundry does so.</p>
<p>The invention of mechanical washing machines eliminated a great amount
of human suffering and toil. Machines do the washing now. Nobody has
to break their back scrubbing soiled linens against a washboard.</p>
<p><img src="https://pic.blog.plover.com/misc/dirty-jobs/washboard.jpg" class="center" alt="The black and
white photograph depicts a young child, perhaps two or three years
old, standing next to a wooden wash tub and washboard. The child is
wearing a fur parka with a fur-trimmed hood.The washboard is positioned at a slight angle, resting partially within the wooden tub. It appears weathered and worn, suggesting frequent use. The tub itself is simple, made of wood staves. A small amount of what looks like clothing or fabric is partially visible inside the tub. The ground is primarily sandy and uneven, with sparse vegetation or debris visible."><span
style="font-size: x-small">“Eskimo child with wooden tub and
washboard”, c. 1905, by <a href="https://en.wikipedia.org/wiki/Frank_Hamilton_Nowell">Frank
Hamilton Nowell</a>, public domain, via
<a href="https://commons.wikimedia.org/wiki/File:Eskimo_child_with_wooden_tub_and_washboard,_ca_1905_(NOWELL_167).jpeg">Wikimedia Commons</a>.</span></p>
<p>But the flip side of that is that there are still poor and
marginalized people, who now have to find other work. Mechanical
laundry has taken away their jobs. They no longer have to do the
backbreaking labor of hand laundry. Now they have the option to
starve to death instead.</p>
<p>Is it a net win? I don't know. I'd like to think so. I'd like to
free people from the toil of hand laundry <em>without</em> also starving some
of them to death. Our present system doesn't seem to be very good at
that sort of thing. I'm not sure what a better system would look
like.</p>
<p>Anyway, this is on my mind a lot lately because of the recent
developments in computer-generated art. I think “well, it's not all
bad, because at least now nobody will have to make a living drawing
pornographic pictures of other people's furry OCs. Surely that is a
slight elevation of the human condition.” On the other hand, some of
those people would rather have the money and who am I to deny them
that choice?</p>
-
The mathematical past is a foreign country
https://blog.plover.com/2025/03/25#peano-starts-at-1
<p>A modern presentation of the <a href="https://en.wikipedia.org/wiki/Peano_axioms">Peano axioms</a> looks like
this:</p>
<ol>
<li><img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%24"> is a natural number</li>
<li>If <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%24"> is a natural number, then so is the result of appending an
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24S%24"> to the beginning of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24n%24"></li>
<li>Nothing else is a natural number</li>
</ol>
<p>This baldly states that zero is a natural number.</p>
<p>I think this is a 20th-century development. In 1889, the natural
numbers started at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%24">, not at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%24">. Peano's
<a href="https://archive.org/details/arithmeticespri00peangoog/page/n6/mode/2up"><em>Arithmetices principia, nova methodo exposita</em></a>
(1889) is the source of the Peano axioms and in it Peano starts the
natural numbers at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%24">, not at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%240%24">:</p>
<p><a href="https://pic.blog.plover.com/math/peano-starts-at-1/Peano-1.png"><img src="https://pic.blog.plover.com/math/peano-starts-at-1/axiom-1.png"
class="center" alt="screencap of Peano's first axiom from his book.
It states “1∈N”."/></a></p>
<p>There's axiom 1: <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%5cin%5cBbb%20N%24">. No zero. I think starting at <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%200%24">
may be a Bourbakism.</p>
<p>In a modern presentation we define addition like this: </p>
<p>$$
\begin{array}{rrl}
(i) & a + 0 = & a \\
(ii) & a + Sb = & S(a+b)
\end{array}
$$</p>
<p>Peano doesn't have zero, so he doesn't need item <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%28i%29%24">. His definition
just has <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%28ii%29%24">.</p>
<p>But wait, doesn't his inductive definition need to have a base case? Maybe something like this?</p>
<p>\begin{array}{rrl}
(i') & a + 1 = & Sa \\
\end{array}</p>
<p>Nope, Peano has nothing like that. But surely the definition must
have a base case? How can Peano get around that?</p>
<p>Well, by modern standards, he cheats!</p>
<p>Peano doesn't have a special notation like <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24S%24"> for successor. Where
a modern presentation might write <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24Sa%24"> for the successor of the
number <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24a%24">, Peano writes “<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24a%20%2b%201%24">”.</p>
<p>So his version of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%28ii%29%24"> looks like this:</p>
<p>$$
a + (b + 1) = (a + b) + 1
$$</p>
<p>which is pretty much a symbol-for-symbol translation of <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%28ii%29%24">. But
if we try to translate <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%28i%27%29%24"> similarly, it looks like this:</p>
<p>$$
a + 1 = a + 1
$$</p>
<p>That's why Peano didn't include it: to him, it was tautological.</p>
<p>But to modern eyes that last formula is deceptive because it
equivocates between the "<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%2b%201%24">" notation that is being used to
represent the successor operation (on the right) and the addition
operation that Peano is trying to define (on the left). In a modern
presentation, we are careful to distinguish between our formal symbol
for a successor, and our definition of the addition operation. </p>
<p>Peano, working pre-Frege and pre-Hilbert, doesn't have the same
concept of what this means. To Peano, constructing the successor of a
number, and adding a number to the constant <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%24">, are the <em>same</em>
operation: the successor operation <em>is</em> just adding <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%241%24">.</p>
<p>But to us, <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24Sa%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24a%2bS0%24"> are different operations that happen to
yield the same value. To us, the successor operation is a purely
abstract or formal symbol manipulation (“stick an <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24S%24"> on the
front”). The fact that it also has an arithmetic interpretation,
related to addition, appears only once we contemplate the theorem
$$\forall a. a + S0 = Sa.$$ There is nothing like this in Peano.</p>
<p>It's things like this that make it tricky to read older mathematics
books. There are deep philosophical differences about what is being
done and why, and they are not usually explicit.</p>
<p>Another example: in the 19th century, the abstract presentation of
group theory had not yet been invented. The phrase “group” was
understood to be short for “group of permutations”, and the important
property was <em>closure</em>, specifically closure under composition of
permutations. In a 20th century abstract presentation, the closure
property is usually passed over without comment. In a modern view, the
notation <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24G_1%5ccup%20G_2%24"> is not even meaningful, because groups are
not sets and you cannot just mix together two sets of group elements
without also specifying how to extend the binary operation, perhaps
via a free product or something. In the 19th century, <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24G_1%5ccup%20G_2%24">
is perfectly ordinary, because <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24G_1%24"> and <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24G_2%24"> are just sets of
permutations. One can then ask whether that set is a group — that is,
whether it is closed under composition of permutations — and if not,
what is the smallest group that contains it.</p>
<p>It's something like a foreign language of a foreign
culture. You can try to translate the words, but the underlying ideas
may not be the same.</p>
<h3>Addendum 20250326</h3>
<p>Simon Tatham reminds me that Peano's equivocation has come up here
before.
<a href="https://blog.plover.com/math/se/2023-10.html#addition-with-successor">I previously discussed</a>
a Math SE post
<a href="https://math.stackexchange.com/q/4795154">in which OP was confused</a>
because Bertrand Russell's presentation of the Peano axioms similarly
used the notation “<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24%2b%201%24">” for the successor operation, and did not
understand why it was not tautological.</p>
-
Baseball on the Moon
https://blog.plover.com/2025/03/18#lunar-baseball
<p>We want to adapt baseball to be played on the moon. Is there any way
to make it work?</p>
<p>My first impression is: no, for several reasons.</p>
<p>The pitched ball will go a little faster (no air resistance) but
breaking balls are impossible (ditto). So the batter will find it
easier to get a solid hit. We can't fix this by moving the plate
closer to the pitcher's rubber; that would expose both batter and
pitcher to unacceptable danger. I think we also can't fix it by making
the plate much wider.</p>
<p>Once the batter hits the ball, it will go a long long way, six times
as far as a batted ball on Earth. In order for every hit to not be a
home run, the outfield fence will have to be about six times as far
way, so the outfield will be <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%2436%24"> times as large. I don't think the
outfielders can move six times as fast to catch up to it. Perhaps if
there were 100 outfielders instead of only three?</p>
<p>Fielding the ball will be more difficult. Note that even though the
vacuum prevents the <em>pitch</em> from breaking, the batted ball can still
take unexpected hops off the ground.</p>
<p>Having gotten hold of the ball, the outfielder will then need to throw
it back to the infield. They will be able to throw it that far, but
they probably won't be able do it accurately enough for the receiving
fielder to make the play at the base. More likely the outfielder will
throw it wild.</p>
<p>I don't think this can be easily salvaged. People do love home runs,
but I don't think they would love this. Games are too long already.</p>
<p>Well, here's a thought. What if instead of four bases, arranged in a
<img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%2490%24">-foot square, we had, I don't know, eight or ten, maybe <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24200%24">
or <img src="https://chart.apis.google.com/chart?chf=bg,s,00000000&cht=tx&chl=%24300%24"> feet apart? More opportunities for outs on the basepaths,
and also the middle bases would not be so far from the outfield.
Instead of throwing directly to the infield, the outfielders would
have a relay system where one outfielder would throw to another that
was farther in, and perhaps one more, before reaching the infield.
That might be pretty cool.</p>
<p>I think it's not easy to run fast on the Moon. On the Earth, a
runner's feet are pushing against the ground many times each second.
On the Moon, the runner is taking big leaps. They may only get in
one-sixth as many steps over the same distance, which would give them
much less opportunity to convert muscle energy into velocity.
(Somewhat countervailing, though: no air resistance.) Runners would
have to train specially to be able to leap accurately to the bases.
Under standard rules, a runner who overshoots the base will land off
the basepaths and be automatically out.</p>
<p>So we might expect to see the runner bounding toward first base. Then
one of the thirty or so far-left fielders would get the ball, relay it
to the middle-left fielder and then the near-left fielder who would
make the throw back to first. The throw would be inaccurate because
it has to traverse a very large infield, and the first baseman would
have to go chasing after it and pick it up from foul territory. He
can't get back to first base quickly enough, but that's okay, the
pitcher has bounded over from the mound and is waiting near first base
to make the force play. Maybe the runner isn't there yet because one
of his leaps was too <em>long</em> and to take another he has to jump high
into the air and come down again.</p>
<p>It would work better than Quiddich, anyway.</p>