<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="../feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Susam's Programming Pages</title>
  <subtitle>Feed for Susam's Programming Pages</subtitle>
  <link href="https://susam.net/"/>
  <link href="https://susam.net/tag/programming.xml" rel="self"/>
  <id>https://susam.net/tag/programming.xml</id>
  <updated>2026-05-03T00:00:00Z</updated>
  <author><name>Susam Pal</name></author>
  <entry>
    <title>QuickQWERTY 1.2.3</title>
    <link href="https://susam.net/code/news/quickqwerty/1.2.3.html"/>
    <id>urn:uuid:d1c2f6bb-8a47-4785-959b-167b8c1fa13a</id>
    <updated>2026-05-03T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;QuickQWERTY 1.2.3&lt;/a&gt; is now
  available.  QuickQWERTY is a web-based touch typing tutor for QWERTY
  keyboards that runs directly in the browser.
&lt;/p&gt;
&lt;p&gt;
  This release includes two small bug fixes.  In the
  &lt;a href=&quot;1.2.2.html&quot;&gt;previous release&lt;/a&gt;, QuickQWERTY source code
  management moved from GitHub to Codeberg.  During that update, the
  licence link in the footer was updated incorrectly.  The broken
  licence link has now been fixed.
&lt;/p&gt;
&lt;p&gt;
  Further, there was a minor bug that caused a redundant dialog box to
  appear while switching between 6-7 split and 5-6 split.  Units 16 to
  20 contain two links labeled &apos;6-7 split&apos; and &apos;5-6 split&apos; which allow
  you to select how you want to split the number keys between left and
  right hands.  Clicking either of those links brings up a dialog that
  explains what the two splits mean and prompts you to confirm in
  order to make the switch.  Say, the &apos;6-7 split&apos; was already the
  chosen split.  Clicking the &apos;6-7 split&apos; label triggered the dialog
  box unnecessarily.  A dialog was unnecessary in that case since if
  you were already on the &apos;6-7 split&apos;, clicking the &apos;6-7 split&apos; label
  resulted in no switch.  This unnecessary dialog has now been
  eliminated.
&lt;/p&gt;
&lt;p&gt;
  To try out QuickQWERTY, please visit
  &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.  The
  source code of QuickQWERTY is available under the terms of the MIT
  licence at &lt;a href=&quot;https://codeberg.org/susam/quickqwerty&quot;&gt;codeberg.org/susam/quickqwerty&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/quickqwerty/1.2.3.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>QuickQWERTY 1.2.2</title>
    <link href="https://susam.net/code/news/quickqwerty/1.2.2.html"/>
    <id>urn:uuid:35d44ea0-50fb-4fa7-9057-6f08f214a800</id>
    <updated>2026-04-28T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;QuickQWERTY 1.2.2&lt;/a&gt; is now
  available.  QuickQWERTY is a web-based touch typing tutor for QWERTY
  keyboards that runs directly in the browser.
&lt;/p&gt;
&lt;p&gt;
  This release includes two important changes.  First, a longstanding
  bug in the practice pane has been fixed.  While practising a lesson,
  a &apos;Restart&apos; link appears in the practice pane.  Due to a bug,
  clicking it incorrectly sent users to Unit 1.1 instead of restarting
  the current lesson.  The link now correctly restarts the active
  lesson.
&lt;/p&gt;
&lt;p&gt;
  Second, source code hosting has moved to
  &lt;a href=&quot;https://codeberg.org/susam/quickqwerty&quot;&gt;Codeberg&lt;/a&gt;.
  Codeberg is the third home of this 17 year old project.  QuickQWERTY
  was first hosted on
  &lt;a href=&quot;https://sourceforge.net/projects/quickqwerty/&quot;&gt;SourceForge&lt;/a&gt;
  in 2008, where it remained for seven years.  In 2015, the project
  moved to GitHub.  It has now moved once again, this time to Codeberg.
&lt;/p&gt;
&lt;p&gt;
  As before, QuickQWERTY continues to be available under the terms of
  the MIT licence.  The latest version remains available on this
  website at &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/quickqwerty/1.2.2.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>HN Skins 0.4.0</title>
    <link href="https://susam.net/code/news/hnskins/0.4.0.html"/>
    <id>urn:uuid:2fd608c6-887a-4b10-b961-5f231cb41a6c</id>
    <updated>2026-03-10T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  HN Skins 0.4.0 is a minor update to HN Skins, a web browser
  userscript that adds custom themes to Hacker News and lets you
  browse HN with a variety of visual styles.  This release introduces
  a small fix to preserve the commemorative black bar that
  occasionally appears at the top of the page.
&lt;/p&gt;
&lt;p&gt;
  When a notable figure in technology or science passes away, Hacker
  News places a thin black bar at the top of the page in tribute.
  Previously some skins could obscure this element.  This update
  ensures that the bar remains visible and clearly noticeable.  In
  dark themed skins, the black bar is rendered as a lighter shade of
  grey so that it maintains sufficient contrast and remains
  conspicuous.
&lt;/p&gt;
&lt;p&gt;
  Today Hacker News has
  &lt;a href=&quot;https://news.ycombinator.com/item?id=47324054&quot;&gt;a story
  about Tony Hoare passing away&lt;/a&gt;, which made me notice that the
  commemorative black bar was not rendered properly with some skins.
  This prompted me to investigate the issue and implement the fix
  included in this release.
&lt;/p&gt;
&lt;p&gt;
  Screenshots showing how the bar appears with different skins are
  available at
  &lt;a href=&quot;https://susam.github.io/blob/img/hnskins/0.4.0/&quot;&gt;susam.github.io/blob/img/hnskins/0.4.0/&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  To install HN Skins,
  visit &lt;a href=&quot;https://github.com/susam/hnskins#readme&quot;&gt;github.com/susam/hnskins&lt;/a&gt;
  and follow the instructions there.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/hnskins/0.4.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>HN Skins 0.3.0</title>
    <link href="https://susam.net/code/news/hnskins/0.3.0.html"/>
    <id>urn:uuid:baafca23-8068-4b32-85b3-4f4aadbbf581</id>
    <updated>2026-03-07T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  HN Skins 0.3.0 is a minor update to HN Skins, a web browser
  userscript that adds custom themes to Hacker News and allows you to
  browse HN with a variety of visual styles.  This release includes
  fixes for a few issues that slipped through earlier versions.  For
  example, the comment input textbox now uses the same font face and
  size as the rest of the active theme.  The colour of visited links
  has also been slightly muted to make it easier to distinguish them
  from unvisited links.  In addition, some skins have been renamed:
  Teletype is now called Courier and Nox is now called Midnight.
&lt;/p&gt;
&lt;p&gt;
  Further, the font face of several monospace based themes is now set
  to &lt;code&gt;monospace&lt;/code&gt; instead of &lt;code&gt;courier&lt;/code&gt;.  This
  allows the browser&apos;s preferred monospace font to be used.  The font
  face of the Courier skin (formerly known as Teletype) remains set
  to &lt;code&gt;courier&lt;/code&gt;.  This will never change because the sole
  purpose of this skin is to celebrate this legendary font.
&lt;/p&gt;
&lt;p&gt;
  To view screenshots of HN Skins or install it, visit
  &lt;a href=&quot;https://github.com/susam/hnskins#readme&quot;&gt;github.com/susam/hnskins&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/hnskins/0.3.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>HN Skins 0.2.0</title>
    <link href="https://susam.net/code/news/hnskins/0.2.0.html"/>
    <id>urn:uuid:15679d71-ed9d-4db4-a1ad-b94a9d2b72be</id>
    <updated>2026-03-01T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  HN Skins 0.2.0 is a minor update of HN Skins.  It comes a day after
  its &lt;a href=&quot;0.1.0.html&quot;&gt;initial release&lt;/a&gt; in order to fine tune a
  few minor issues with the styles in the initial release.  HN Skins
  is a web browser userscript that adds custom themes to Hacker News
  and allows you to browse HN with different visual styles.
&lt;/p&gt;
&lt;p&gt;
  This update removes excessive vertical space below the &apos;reply&apos;
  links, sorts the skin options alphabetically in the selection dialog
  and fixes the background colour of the navigation bar in the
  Terminal skin by changing it from a dark grey to a dark green.
&lt;/p&gt;
&lt;p&gt;
  Soon after making this release, I discovered a few other minor
  issues, such as the Cafe and Terminal themes using Courier when I
  intended them to use the system monospace font.  This has already
  been fixed in the development version currently available on GitHub.
  However, I will make a formal release later.
&lt;/p&gt;
&lt;p&gt;
  See the &lt;a href=&quot;https://github.com/susam/hnskins/blob/main/CHANGES.md&quot;&gt;changelog&lt;/a&gt;
  for more details.  To see some screenshots of HN Skins or to install
  it, visit &lt;a href=&quot;https://github.com/susam/hnskins#readme&quot;&gt;github.com/susam/hnlinks&lt;/a&gt;
  and follow the instructions there.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/hnskins/0.2.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>HN Skins 0.1.0</title>
    <link href="https://susam.net/code/news/hnskins/0.1.0.html"/>
    <id>urn:uuid:810a59fc-4dd4-4631-8426-172165338bca</id>
    <updated>2026-02-28T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  HN Skins 0.1.0 is the initial release of HN Skins, a browser
  userscript that adds custom themes to Hacker News (HN).  It allows
  you to browse HN in style with a selection of visual skins.
&lt;/p&gt;
&lt;p&gt;
  To use HN Skins, first install a userscript manager such as
  Greasemonkey, Tampermonkey or Violentmonkey in your web browser.
  Once installed, you can install HN Skins from
  &lt;a href=&quot;https://github.com/susam/hnskins#readme&quot;&gt;github.com/susam/hnskins&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  The source code is available under the terms of the MIT licence.
  For usage instructions and screenshots, please visit
  &lt;a href=&quot;https://github.com/susam/hnskins#readme&quot;&gt;github.com/susam/hnskins&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/hnskins/0.1.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Twenty Five Years of Computing</title>
    <link href="https://susam.net/twenty-five-years-of-computing.html"/>
    <id>urn:uuid:075ff749-37ef-415f-be76-61ae94a23cfc</id>
    <updated>2026-02-06T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  Last year, I completed 20 years in professional software
  development.  I wanted to write a post to mark the occasion back
  then, but couldn&apos;t find the time.  This post is my attempt to make
  up for that omission.  In fact, I have been involved in software
  development for a little longer than 20 years.  Although I had
  my &lt;a href=&quot;fd-100.html&quot;&gt;first taste&lt;/a&gt; of computer programming as
  a child, it was only when I entered university about 25 years ago
  that I seriously got into software development.  So I&apos;ll start my
  stories from there.  These stories are less about software and more
  about people.  Unlike many posts of this kind, this one offers no
  wisdom or lessons.  It only offers a collection of stories.  I hope
  you&apos;ll like at least a few of them.
&lt;/p&gt;
&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#viewing-the-source&quot;&gt;Viewing the Source&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#reset-vector&quot;&gt;The Reset Vector&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#man-in-the-middle&quot;&gt;Man in the Middle&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#sphagetti-code&quot;&gt;Sphagetti Code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#animated-television-widgets&quot;&gt;Animated Television Widgets&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#good-blessings&quot;&gt;Good Blessings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-ctf-scoreboard&quot;&gt;The CTF Scoreboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;viewing-the-source&quot;&gt;Viewing the Source&lt;/h2&gt;
&lt;p&gt;
  The first story takes place in 2001, shortly after I joined
  university.  One evening, I went to the university computer
  laboratory to browse the World Wide Web.  Out of curiosity, I typed
  &lt;code&gt;susam.com&lt;/code&gt; into the address bar and landed on
  &lt;a href=&quot;https://web.archive.org/web/20010721163153/http://susam.com/&quot;&gt;its
  home page&lt;/a&gt;.  I remember the text and banner looking much larger
  back then.  Display resolutions were lower, so the text and banner
  covered almost half the screen.  I knew very little about the
  Internet then and I was just trying to make sense of it.  I remember
  wondering what it would take to create my own website, perhaps at
  &lt;code&gt;susam.com&lt;/code&gt;.  That&apos;s when an older student who had been
  watching me browse over my shoulder approached and asked if I had
  created the website.  I told him I hadn&apos;t and that I had no idea how
  websites were made.  He asked me to move aside, took my seat and
  clicked View &amp;gt; Source in Internet Explorer.  He then explained
  how websites are made of HTML pages and how those pages are simply
  text instructions.
&lt;/p&gt;
&lt;p&gt;
  Next, he opened Notepad and wrote a simple HTML page that looked
  something like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;BODY&amp;gt;&amp;lt;FONT COLOR=&quot;RED&quot;&amp;gt;HELLO&amp;lt;/FONT&amp;gt;&amp;lt;/BODY&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Yes, we had a &lt;code&gt;FONT&lt;/code&gt; tag back then and it was common
  practice to write HTML tags in uppercase.  He then opened the page
  in a web browser and showed how it rendered.  After that, he
  demonstrated a few more features such as changing the font face and
  size, centring the text and altering the page&apos;s background colour.
  Although the tutorial lasted only about ten minutes, it made the Web
  feel far less mysterious and much more fascinating.
&lt;/p&gt;
&lt;p&gt;
  That person had an ulterior motive though.  After the tutorial, he
  never returned the seat to me.  He just continued browsing the Web
  and waited for me to leave.  I was too timid to ask for my seat
  back.  Seats were limited, so I returned to my dorm room both
  disappointed that I couldn&apos;t continue browsing that day and excited
  about all the websites I might create with this newfound knowledge.
  I could never register &lt;code&gt;susam.com&lt;/code&gt; for myself though.
  That domain was always used by some business selling Turkish
  cuisines.  Eventually, I managed to get the next best thing:
  a &lt;code&gt;.net&lt;/code&gt; domain of my own.  That brief encounter in the
  university laboratory set me on a lifelong path of creating and
  maintaining personal websites.
&lt;/p&gt;
&lt;h2 id=&quot;reset-vector&quot;&gt;The Reset Vector&lt;/h2&gt;
&lt;p&gt;
  The second story also comes from my university days.  One afternoon,
  I was hanging out with my mates in the computer laboratory.  In
  front of me was an MS-DOS machine powered by an Intel 8086
  microprocessor, on which I was writing a lift control program in
  assembly.  In those days, it was considered important to
  deliberately practise solving made-up problems as a way of honing
  our programming skills.  As I worked on my program, my mind drifted
  to a small detail about the 8086 microprocessor that we had recently
  learnt in a lecture.  Our professor had explained that, when the
  8086 microprocessor is reset, execution begins with CS:IP set to
  FFFF:0000.  So I murmured to anyone who cared to listen, &apos;I wonder
  if the system will reboot if I jump to FFFF:0000.&apos;  I then
  opened &lt;code&gt;DEBUG.EXE&lt;/code&gt; and jumped to that address.
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;C:\&amp;gt;&lt;kbd&gt;DEBUG&lt;/kbd&gt;
-&lt;kbd&gt;G =FFFF:0000&lt;/kbd&gt;&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  The machine rebooted instantly.  One of my friends, who topped the
  class every semester, had been watching over my shoulder.  As soon
  as the machine restarted, he exclaimed, &apos;How did you do that?&apos;  I
  explained that the reset vector is located at physical address FFFF0
  and that the CS:IP value FFFF:0000 maps to that address in real
  mode.  After that, I went back to working on my lift control program
  and didn&apos;t think much more about the incident.
&lt;/p&gt;
&lt;p&gt;
  About a week later, the same friend came to my dorm room.  He sat
  down with a grave look on his face and asked, &apos;How did you know to
  do that?  How did it occur to you to jump to the reset vector?&apos;  I
  must have said something like, &apos;It just occurred to me.  I
  remembered that detail from the lecture and wanted to try it out.&apos;
  He then said, &apos;I want to be able to think like that.  I come top of
  the class every semester, but I don&apos;t think the way you do.  I would
  never have thought of taking a small detail like that and testing it
  myself.&apos;  I replied that I was just curious to see whether what we
  had learnt actually worked in practice.  He responded, &apos;And that&apos;s
  exactly it.  It would never occur to me to try something like that.
  I feel disappointed that I keep coming top of the class, yet I am
  not curious in the same way you are.  I&apos;ve decided I don&apos;t want to
  top the class anymore.  I just want to explore and experiment with
  what we learn, the way you do.&apos;
&lt;/p&gt;
&lt;p&gt;
  That was all he said before getting up and heading back to his dorm
  room.  I didn&apos;t take it very seriously at the time.  I couldn&apos;t
  imagine why someone would willingly give up the accomplishment of
  coming first every semester.  But he kept his word.  He never topped
  the class again.  He still ranked highly, often within the top ten,
  but he kept his promise of never finishing first again.  To this
  day, I feel a mix of embarrassment and pride whenever I recall that
  incident.  With a single jump to the processor&apos;s reset entry point,
  I had somehow inspired someone to step back from academic
  competition in order to have more fun with learning.  Of course,
  there is no reason one cannot do both.  But in the end, that was his
  decision, not mine.
&lt;/p&gt;
&lt;h2 id=&quot;man-in-the-middle&quot;&gt;Man in the Middle&lt;/h2&gt;
&lt;p&gt;
  In my first job after university, I was assigned to a technical
  support team where part of my work involved running an installer to
  deploy a specific component of an e-banking product for customers,
  usually large banks.  As I learnt to use the installer, I realised
  how fragile it was.  The installer, written in Python, often failed
  because of incorrect assumptions about the target environment and
  almost always required some manual intervention to complete
  successfully.  During my first week on the project, I spent much of
  my time stabilising the installer and writing a step-by-step user
  guide explaining how to use it.  The result was well received by
  both my seniors and management.  To my surprise, the user guide
  received more praise than the improvements I made to the installer.
  While the first few weeks were productive, I soon realised I would
  not find the work fulfilling for long.  I wrote to management a few
  times to ask whether I could transfer to a team where I could work
  on something more substantial.
&lt;/p&gt;
&lt;p&gt;
  My emails were initially met with resistance.  After several rounds
  of discussion, someone who had heard about my situation reached out
  and suggested a team whose manager might be interested in
  interviewing me.  The team was based in a different city.  I was
  young and willing to relocate wherever I could find good work, so I
  immediately agreed to the interview.
&lt;/p&gt;
&lt;p&gt;
  This was in 2006, when video conferencing was not yet common.  On
  the day of the interview, the hiring manager called me on my office
  desk phone.  He began by introducing the team, which was called
  &lt;em&gt;Archie&lt;/em&gt;, short for &lt;em&gt;architecture&lt;/em&gt;.  The team
  developed and maintained the web framework and core architectural
  components on which the entire e-banking product was built.  The
  product had existed long before open source frameworks such as
  Spring or Django came into existence, so features such as API
  routing, authentication and authorisation layers, cookie management,
  etc. were all implemented in-house as Java Servlets and JavaServer
  Pages (JSP).  Since the software was used in banking environments,
  it also had to pass security testing and regular audits to minimise
  the risk of serious flaws.
&lt;/p&gt;
&lt;p&gt;
  The interview began well.  He asked several questions related to
  software security, such as what SQL injection is, how it can be
  prevented and how one might design a web framework that mitigates
  cross-site scripting attacks.  He also asked me a few programming
  questions, most which I answered pretty well.  Towards the end,
  however, he asked how we could prevent MITM attacks.  I had never
  heard the term, so I admitted that I did not know what MITM meant.
  He then asked, &apos;Man in the middle?&apos; but I still had no idea what
  that meant or whether it was even a software engineering concept.
  He replied, &apos;Learn everything you can about PKI and MITM.  We need
  to build a digital signatures feature for one of our corporate
  banking products.  That&apos;s the first thing we&apos;ll work on.&apos;
&lt;/p&gt;
&lt;p&gt;
  Over the next few weeks, I studied RFCs and documentation related to
  public key infrastructure, public key cryptography standards and
  related topics.  At first, the material felt intimidating, but after
  spending time each evening reading whatever relevant literature I
  could find, things gradually began to make sense.  Concepts that
  initially seemed complex and overwhelming eventually felt intuitive
  and elegant.  I relocated to the new city a few weeks later and
  delivered the digital signatures feature about a month after joining
  the team.  We used the open source Bouncy Castle library to
  implement the feature.  After that project, I worked on other parts
  of the product too.  The most rewarding part was knowing that the
  code I was writing became part of a mature product used by hundreds
  of banks and millions of users.  It was especially satisfying to see
  the work pass security testing and audits and be considered ready
  for release.
&lt;/p&gt;
&lt;p&gt;
  That was my first real engineering job.  My manager also turned out
  to be an excellent mentor.  Working with him helped me develop new
  skills and his encouragement gave me confidence that stayed with me
  for years.  Nearly two decades have passed since then, yet the
  product is still in service and continues to be actively developed.
  In fact, in my current phase of life I sometimes encounter it as a
  customer.  Occasionally, I open the browser&apos;s developer tools to
  view the page source where I can still see traces of the HTML
  generated by code I wrote almost twenty years ago.
&lt;/p&gt;
&lt;h2 id=&quot;sphagetti-code&quot;&gt;Sphagetti Code&lt;/h2&gt;
&lt;p&gt;
  Around 2007 or 2008, I began working on a proof of concept for
  developing widgets for an OpenTV set-top box.  The work involved
  writing code in a heavily trimmed-down version of C.  One afternoon,
  while making good progress on a few widgets, I noticed that they
  would occasionally crash at random.  I tried tracking down the bugs,
  but I was finding it surprisingly difficult to understand my own
  code.  I had managed to produce some truly spaghetti code full of
  dubious pointer operations that were almost certainly responsible
  for the crashes, yet I could not pinpoint where exactly things were
  going wrong.
&lt;/p&gt;
&lt;p&gt;
  Ours was a small team of four people, each working on an independent
  proof of concept.  The most senior person on the team acted as our
  lead and architect.  Later that afternoon, I showed him my progress
  and explained that I was still trying to hunt down the bugs causing
  the widgets to crash.  He asked whether he could look at the code.
  After going through it briefly and probably realising that it was a
  bit of a mess, he asked me to send him the code as a tarball, which
  I promptly did.
&lt;/p&gt;
&lt;p&gt;
  He then went back to his desk to study the code.  I remember
  thinking that there was no way he was going to find the problem
  anytime soon.  I had been debugging it for hours and barely
  understood what I had written myself; it was the worst spaghetti
  code I had ever produced.  With little hope of a quick solution, I
  went back to debugging on my own.
&lt;/p&gt;
&lt;p&gt;
  Barely five minutes later, he came back to my desk and asked me to
  open a specific file.  He then showed me exactly where the pointer
  bug was.  It had taken him only a few minutes not only to read my
  tangled code but also to understand it well enough to identify the
  fault and point it out.  As soon as I fixed that line, the crashes
  disappeared.  I was genuinely in awe of his skill.
&lt;/p&gt;
&lt;p&gt;
  I have always loved computing and programming, so I had assumed I
  was already fairly good at it.  That incident, however, made me
  realise how much further I still had to go before I could consider
  myself a good software developer.  I did improve significantly in
  the years that followed and today I am far better at managing
  software complexity than I was back then.
&lt;/p&gt;
&lt;h2 id=&quot;animated-television-widgets&quot;&gt;Animated Television Widgets&lt;/h2&gt;
&lt;p&gt;
  In another project from that period, we worked on another set-top
  box platform that supported Java Micro Edition (Java ME) for widget
  development.  One day, the same architect from the previous story
  asked whether I could add animations to the widgets.  I told him
  that I believed it should be possible, though I&apos;d need to test it to
  be sure.  Before continuing with the story, I need to explain how
  the different stakeholders in the project were organised.
&lt;/p&gt;
&lt;p&gt;
  Our small team effectively played the role of the software vendor.
  The final product going to market would carry the brand of a major
  telecom carrier, offering direct-to-home (DTH) television services,
  with the set-top box being one of the products sold to customers.
  The set-top box was manufactured by another company.  So the project
  was a partnership between three parties: our company as the software
  vendor, the telecom carrier and the set-top box manufacturer.  The
  telecom carrier wanted to know whether widgets could be animated on
  screen with smooth slide-in and slide-out effects.  That was why the
  architect approached me to ask whether it could be done.
&lt;/p&gt;
&lt;p&gt;
  I began working on animating the widgets.  Meanwhile, the architect
  and a few senior colleagues attended a business meeting with all the
  partners present.  During the meeting, he explained that we were
  evaluating whether widget animations could be supported.  The
  set-top box manufacturer immediately dismissed the idea, saying,
  &apos;That&apos;s impossible.  Our set-top box does not support animation.&apos;
  When the architect returned and shared this with us, I replied, &apos;I
  do not understand.  If I can draw a widget, I can animate it too.
  All it takes is clearing the widget and redrawing it at slightly
  different positions repeatedly.  In fact, I already have a working
  version.&apos;  I then showed a demo of the animated widgets running on
  the emulator.
&lt;/p&gt;
&lt;p&gt;
  The following week, the architect attended another partners&apos; meeting
  where he shared updates about our animated widgets.  I was not
  personally present, so what follows is second-hand information
  passed on by those who were there.  I learnt that the set-top box
  company reacted angrily.  For some reason, they were unhappy that we
  had managed to achieve results using their set-top box and APIs that
  they had officially described as impossible.  They demanded that we
  stop work on animation immediately, arguing that our work could not
  be allowed to contradict their official position.  At that point,
  the telecom carrier&apos;s representative intervened and bluntly told the
  set-top box representative to just shut up.  If the set-top box guy
  was furious, the telecom guy was even more so, &apos;You guys told us
  animation was not possible and these people are showing that it is!
  You manufacture the set-top box.  How can you not know what it is
  capable of?&apos;
&lt;/p&gt;
&lt;p&gt;
  Meanwhile, I continued working on the proof of concept.  It worked
  very well in the emulator, but I did not yet have access to the
  actual hardware.  The device was still in the process of being
  shipped to us, so all my early proof-of-concepts ran on the
  emulator.  The following week, the architect planned to travel to
  the set-top box company&apos;s office to test my widgets on the real
  hardware.
&lt;/p&gt;
&lt;p&gt;
  At the time, I was quite proud of demonstrating results that even
  the hardware maker believed were impossible.  When the architect
  eventually travelled to test the widgets on the actual device, a
  problem emerged.  What looked like buttery smooth animation on the
  emulator appeared noticeably choppy on a real television.  Over the
  next few weeks, I experimented with frame rates, buffering
  strategies and optimising the computation done in the the rendering
  loop.  Each week, the architect travelled for testing and returned
  with the same report: the animation had improved somewhat, but it
  still remained choppy.  The modest embedded hardware simply could
  not keep up with the required computation and rendering.  In the
  end, the telecom carrier decided that no animation was better than
  poor animation and dropped the idea altogether.  So in the end, the
  set-top box developers turned out to be correct after all.
&lt;/p&gt;
&lt;h2 id=&quot;good-blessings&quot;&gt;Good Blessings&lt;/h2&gt;
&lt;p&gt;
  Back in 2009, after completing about a year at RSA Security, I began
  looking for work that felt more intellectually stimulating,
  especially projects involving mathematics and algorithms.  I spoke
  with a few senior leaders about this, but nothing materialised for
  some time.  Then one day, Dr Burt Kaliski, Chief Scientist at RSA
  Laboratories, asked to meet me to discuss my career aspirations.  I
  have written about this in more detail in another post here:
  &lt;a href=&quot;good-blessings.html&quot;&gt;Good Blessings&lt;/a&gt;.  I will summarise
  what followed.
&lt;/p&gt;
&lt;p&gt;
  Dr Kaliski met me and offered a few suggestions about the kinds of
  teams I might approach to find more interesting work.  I followed
  his advice and eventually joined a team that turned out to be an
  excellent fit.  I remained with that team for the next six years.
  During that time, I worked on parser generators, formal language
  specification and implementation, as well as indexing and querying
  engines of a petabyte-scale database.  I learnt something new almost
  every day during those six years.  It remains one of the most
  enjoyable periods of my career.  I have especially fond memories of
  working on parser generators alongside remarkably skilled engineers
  from whom I learnt a lot.
&lt;/p&gt;
&lt;p&gt;
  Years later, I reflected on how that brief meeting with Dr Kaliski
  had altered the trajectory of my career.  I realised I was not sure
  whether I had properly expressed my gratitude to him for the role he
  had played in shaping my path.  So I wrote to thank him and explain
  how much that single conversation had influenced my life.  A few
  days later, Dr Kaliski replied, saying he was glad to know that the
  steps I took afterwards had worked out well.  Before ending his
  message, he wrote this heart-warming note:
&lt;/p&gt;
&lt;blockquote&gt;
  &amp;lsquo;One of my goals is to be able to provide encouragement to
  others who are developing their careers, just as others have
  invested in mine, passing good blessings from one generation to
  another.&amp;rsquo;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-ctf-scoreboard&quot;&gt;The CTF Scoreboard&lt;/h2&gt;
&lt;p&gt;
  This story comes from 2019.  By then, I was no longer a
  twenty-something engineer just starting out.  I was now a
  middle-aged staff engineer with years of experience building both
  low-level networking systems and database systems.  Most of my work
  up to that point had been in C and C++.  I was now entering a new
  phase of my career where I would be leading the development of
  microservices written in Go and Python.  Like many people in this
  profession, computing has long been one of my favourite hobbies.  So
  although my professional work for the previous decade had focused on
  C and C++, I had plenty of hobby projects in other languages,
  including Python and Go.  As a result, switching gears from systems
  programming to application development was a smooth transition for
  me.  I cannot even say that I missed working in C and C++.  After
  all, who wants to spend their days occasionally chasing memory bugs
  in core dumps when you could be building features and delivering
  real value to customers?
&lt;/p&gt;
&lt;p&gt;
  In October 2019, during Cybersecurity Awareness Month, a Capture the
  Flag (CTF) event was organised at our office.  The contest featured
  all kinds of technical puzzles, ranging from SQL injection
  challenges to insecure cryptography problems.  Some challenges also
  involved reversing binaries and exploiting stack overflow issues.
&lt;/p&gt;
&lt;p&gt;
  I am usually rather intimidated by such contests.  The whole idea of
  competitive problem-solving under time pressure tends to make me
  nervous.  But one of my colleagues persuaded me to participate in
  the CTF.  And, somewhat to my surprise, I turned out to be rather
  good at it.  Within about eight hours, I had solved roughly 90% of
  the puzzles.  I finished at the top of the scoreboard.
&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;files/blog/ctf-2019.png&quot; alt=&quot;Scoreboard of a Capture the Flag (CTF) event&quot;&gt;
  &lt;figcaption&gt;
    CTF Scoreboard
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
  In my younger days, I was generally known to be a good problem
  solver.  I was often consulted when thorny problems needed solving
  and I usually managed to deliver results.  I also enjoyed solving
  puzzles.  I had a knack for them and happily spent hours, sometimes
  days, working through obscure mathematical or technical puzzles and
  sharing detailed write-ups with friends of the nerd variety.  Seen
  in that light, my performance at the CTF probably should not have
  surprised me.  Still, I was very pleased.  It was reassuring to know
  that I could still rely on my systems programming experience to
  solve obscure challenges.
&lt;/p&gt;
&lt;p&gt;
  During the course of the contest, my performance became something of
  a talking point in the office.  Colleagues occasionally stopped by
  my desk to appreciate my progress in the CTF.  Two much younger
  colleagues, both engineers I admired for their skill and
  professionalism, were discussing the results nearby.  They were
  speaking softly, but I could still overhear parts of their
  conversation.  Curious, I leaned slightly and listened a bit more
  carefully.  I wanted to know what these two people, whom I admired a
  lot, thought about my performance.
&lt;/p&gt;
&lt;p&gt;
  One of them remarked on how well I was doing in the contest.  The
  other replied, &apos;Of course he is doing well.  He has more than ten
  years of experience in C.&apos;  At that moment, I realised that no
  matter how well I solved those puzzles, the result would naturally
  be credited to experience.  In my younger days, when I solved tricky
  problems like these, people would sometimes call me smart.  Now
  people simply saw it as a consequence of my experience.  Not that I
  particularly care for labels such as &apos;smart&apos; anyway, but it did make
  me realise how things had changed.  I was now simply the person with
  many years of experience.  Solving technical puzzles that involved
  disassembling binaries, tracing execution paths and reconstructing
  program logic was expected rather than remarkable.
&lt;/p&gt;
&lt;p&gt;
  I continue to sharpen my technical skills to this day.  While my
  technical results may now simply be attributed to experience, I hope
  I can continue to make a good impression through my professionalism,
  ethics and kindness towards the people I work with.  If those leave
  a lasting impression, that is good enough for me.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/twenty-five-years-of-computing.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Jan '26 Notes</title>
    <link href="https://susam.net/26a.html"/>
    <id>urn:uuid:77759acf-97e2-4ded-b57e-12449baeb13d</id>
    <updated>2026-01-29T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  In these monthly notes, I jot down ideas and references I
  encountered during the month that I did not have time to expand into
  their own posts.  A few of these may later develop into independent
  posts but most of them will likely not.  In any case, this format
  ensures that I record them here.  I spent a significant part of this
  month studying the book &lt;em&gt;Algebraic Graph Theory&lt;/em&gt; by Godsil
  and Royle, so many of the notes here are about it.  There are a few
  non-mathematical, technical notes towards the end.
&lt;/p&gt;
&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#cayley-graphs&quot;&gt;Cayley Graphs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#vertex-transitive-graphs&quot;&gt;Vertex-Transitive Graphs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#arc-transitive-graphs&quot;&gt;Arc-Transitive Graphs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bipartite-graphs-and-cycle-parity&quot;&gt;Bipartite Graphs and Cycle Parity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tutte-theorem&quot;&gt;Tutte&apos;s Theorem&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tutte-8-cage&quot;&gt;Tutte&apos;s 8-Cage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#lcg&quot;&gt;Linear Congruential Generator&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#cat-n&quot;&gt;Numbering Lines&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;cayley-graphs&quot;&gt;Cayley Graphs&lt;/h2&gt;
&lt;p&gt;
  Let \( G \) be a group and let \( C \subseteq G \) such that \( C \)
  is closed under taking inverses and does not contain the identity,
  i.e.

  \[
    \forall x \in C, \; x^{-1} \in C, \qquad e \notin C.
  \]

  Then the Cayley graph \( X(G, C) \) is the graph with the vertex set
  \( V(X(G, C)) \) and edge set \( E(X(G, C)) \) defined by

  \begin{align*}
    V(X(G, C)) &amp;amp;= G, \\
    E(X(G, C)) &amp;amp;= \{ gh : hg^{-1} \in C \}.
  \end{align*}

  The set \( C \) is known as the connection set.
&lt;/p&gt;
&lt;h2 id=&quot;vertex-transitive-graphs&quot;&gt;Vertex-Transitive Graphs&lt;/h2&gt;
&lt;p&gt;
  A graph \( X \) is &lt;em&gt;vertex-transitive&lt;/em&gt; if its automorphism
  group acts transitively on its set of vertices \( V(X).  \)
  Intuitively, this means that no vertex has a special role.  We can
  &apos;move&apos; the graph around so that any chosen vertex becomes any other
  vertex.  In other words, all vertices are indistinguishable.  The
  graph looks the same from each vertex.
&lt;/p&gt;
&lt;p&gt;
  The \( k \)-cube \( Q_k \) is vertex-transitive.  So are the Cayley
  graphs \( X(G, C).  \)  However the path graph \( P_3 \) is not
  vertex-transitive since no automorphism can send the middle vertex
  of valency \( 2 \) to an end vertex of valency \( 1.  \)
&lt;/p&gt;
&lt;h2 id=&quot;arc-transitive-graphs&quot;&gt;Arc-Transitive Graphs&lt;/h2&gt;
&lt;p&gt;
  The cube \( Q_3 \) is \( 2 \)-arc-transitive but not \( 3
  \)-arc-transitive.  In \( Q_3, \) a \( 3 \)-arc belonging to a \( 4
  \)-cycle cannot be sent to a \( 3 \)-arc that does not belong to a
  \( 4 \)-cycle.  This is easy to explain.  The end vertices of a \( 3
  \)-arc belonging to a \( 4 \)-cycle are adjacent but the end
  vertices of a \( 3 \)-arc not belonging to a \( 4 \)-cycle are not
  adjacent.  Therefore, no automorphism can map the end vertices of
  the first \( 3 \)-arc to those of the second \( 3 \)-arc.
&lt;/p&gt;
&lt;p&gt;
  For intuition, imagine that a traveller stands on a vertex and
  chooses an edge to move along.  They do this \( s \) times thereby
  walking along an arc of length \( s, \) also known as an \( s
  \)-arc.  By the definition of \( s \)-arcs, the traveller is not
  allowed to backtrack from one vertex to the previous one
  immediately.  In an \( s \)-arc-transitive graph, these arcs look
  the same no matter which vertex they start from or which edges they
  choose.  In the cube, this is indeed true for \( s = 2.  \)  All arcs
  of length \( 2 \) are indistinguishable.  No matter which arc of
  length \( 2 \) the traveller has walked along, the graph would look
  the same from their perspective at each vertex along the arc.
  However, this no longer holds good for arcs of length \( 3 \) since
  there are two distinct kinds of arcs of length \( 3.  \)  The first
  kind ends at a distance of \( 1 \) from the starting vertex of the
  arc (when the arc belongs to a \( 4 \)-cycle).  The second kind ends
  at a distance \( 3 \) from the starting vertex of the arc (when the
  arc does not belong to a \( 4 \)-cycle).  Therefore the cube is not
  \( 3 \)-arc-transitive.
&lt;/p&gt;
&lt;h2 id=&quot;bipartite-graphs-and-cycle-parity&quot;&gt;Bipartite Graphs and Cycle Parity&lt;/h2&gt;
&lt;p&gt;
  A graph is bipartite if and only if it contains no cycles of odd
  length.  Equivalently, every cycle in a bipartite graph has even
  length.  Conversely, if every cycle in a graph has even length, then
  the graph is bipartite.
&lt;/p&gt;
&lt;h2 id=&quot;tutte-theorem&quot;&gt;Tutte&apos;s Theorem&lt;/h2&gt;
&lt;p&gt;
  For any \( s \)-arc-transitive cubic graph, \( s \le 5.  \)  This was
  demonstrated by W. T. Tutte in 1947.  A proof can be found in
  Chapter 18 of &lt;em&gt;Algebraic Graph Theory&lt;/em&gt; by Norman Biggs.
&lt;/p&gt;
&lt;p&gt;
  In 1973, Richward Weiss established a more general theorem that
  proves that for any \( s \)-arc-transitive graph, \( s \le 7.  \)
  The bound is weaker but it applies to all graphs rather than only to
  cubic ones.
&lt;/p&gt;
&lt;h2 id=&quot;tutte-8-cage&quot;&gt;Tutte&apos;s 8-Cage&lt;/h2&gt;
&lt;p&gt;
  The book &lt;em&gt;Algebraic Graph Theory&lt;/em&gt; by Godsil and Royle offers
  the following two descriptions of Tutte&apos;s 8-cage on 30 vertices:
&lt;/p&gt;
&lt;blockquote&gt;
  Take the cube and an additional vertex \( \infty.  \)  In each set of
  four parallel edges, join the midpoint of each pair of opposite
  edges by an edge, then join the midpoint of the two new edges by an
  edge, and finally join the midpoint of this edge to \( \infty.  \)
&lt;/blockquote&gt;
&lt;blockquote&gt;
  Construct a bipartite graph \( T \) with the fifteen edges as one
  colour class and the fifteen \( 1 \)-factors as the other, where
  each edge is adjacent to the three \( 1 \)-factors that contain it.
&lt;/blockquote&gt;
&lt;p&gt;
  It can be shown that both descriptions construct a cubic bipartite
  graph on \( 30 \) vertices of girth \( 8.  \)  It can be further
  shown that there is a unique cubic bipartite graph on \( 30 \)
  vertices with girth \( 8.  \)  As a result both descriptions above
  construct the same graph.
&lt;/p&gt;
&lt;h2 id=&quot;lcg&quot;&gt;Linear Congruential Generator&lt;/h2&gt;
&lt;p&gt;
  Here is a simple linear congruential generator (LCG) implementation
  in JavaScript:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function srand (seed) {
  let x = seed
  return function () {
    x = (1664525 * x + 1013904223) % 4294967296
    return x
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Here is an example usage:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;&amp;gt; &lt;kbd&gt;const rand = srand(0)&lt;/kbd&gt;
undefined
&amp;gt; &lt;kbd&gt;rand()&lt;/kbd&gt;
1013904223
&amp;gt; &lt;kbd&gt;rand()&lt;/kbd&gt;
1196435762
&amp;gt; &lt;kbd&gt;rand()&lt;/kbd&gt;
3519870697&lt;/samp&gt;&lt;/pre&gt;
&lt;h2 id=&quot;cat-n&quot;&gt;Numbering Lines&lt;/h2&gt;
&lt;p&gt;
  Both BSD and GNU &lt;code&gt;cat&lt;/code&gt; can number output lines with
  the &lt;code&gt;-n&lt;/code&gt; option.  For example:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;printf &apos;foo\nbar\nbaz\n&apos; | cat -n&lt;/kbd&gt;
     1  foo
     2  bar
     3  baz&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  However I have always used &lt;code&gt;nl&lt;/code&gt; for this.  For example:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;printf &apos;foo\nbar\nbaz\n&apos; | nl&lt;/kbd&gt;
     1  foo
     2  bar
     3  baz&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  While &lt;code&gt;nl&lt;/code&gt; is
  &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9699919799/utilities/nl.html&quot;&gt;specified
  in POSIX&lt;/a&gt;, the &lt;code&gt;cat -n&lt;/code&gt; option
  &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cat.html&quot;&gt;is
  not&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/26a.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/notes.html&quot;&gt;#notes&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/mathematics.html&quot;&gt;#mathematics&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/javascript.html&quot;&gt;#javascript&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/shell.html&quot;&gt;#shell&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>QuickQWERTY 1.2.1</title>
    <link href="https://susam.net/code/news/quickqwerty/1.2.1.html"/>
    <id>urn:uuid:1c7e0e6c-beec-4edf-96b0-84ba22882baf</id>
    <updated>2026-01-27T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  QuickQWERTY 1.2.1 is now available.  QuickQWERTY is a web-based
  touch typing tutor for QWERTY keyboards that runs directly in the
  web browser.
&lt;/p&gt;
&lt;p&gt;
  This release contains a minor bug fix in Unit 4.3.  Unit 4.3 is a
  &apos;Control&apos; unit that lets you practise typing partial words as well
  as full words.  In one place in this unit, the following sequence of
  partial and full words occurs:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;l li lime lime&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  The full word &lt;code&gt;lime&lt;/code&gt; was incorrectly repeated twice.
  This has been fixed to:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;l li lim lime&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  To try out QuickQWERTY, go to
  &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/quickqwerty/1.2.1.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>My Coding Adventures in 2025</title>
    <link href="https://susam.net/code-2025.html"/>
    <id>urn:uuid:3cd2084e-3375-44e8-b88d-db2daf328c15</id>
    <updated>2025-12-24T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  In this post, I return with a retrospective on my coding adventures,
  where I summarise my hobby projects and recreational programming
  activities from the current year.  I did the last such retrospective
  in &lt;a href=&quot;code-2023.html&quot;&gt;2023&lt;/a&gt;.  So I think this is a good
  time to do another retrospective.
&lt;/p&gt;
&lt;p&gt;
  At the outset, I should mention that I have done less hobby
  computing this year than in the past few, largely because I spent a
  substantial portion of my leisure time studying Galois theory and
  algebraic graph theory.  In case you are wondering where I am
  learning these subjects from, the books are &lt;em&gt;Galois Theory&lt;/em&gt;,
  5th ed. by Ian Stewart and &lt;em&gt;Algebraic Graph Theory&lt;/em&gt; by Godsil
  and Royle.  Both are absolutely fascinating subjects and the two
  books I mentioned are quite good as well.  I highly recommend them.
&lt;/p&gt;
&lt;p&gt;
  Now back to the coding adventures.  Here they go:
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;https://github.com/susam/mathb&quot;&gt;MathB&lt;/a&gt;: The year
      began not with the release of a new project but with the
      opposite: discontinuing a project I had maintained for 13 years.
      MathB.in, a mathematics pastebin service, was discontinued early
      this year.  This is a project I developed in 2012 for myself and
      my friends.  Although a rather simple project, it was close to
      my heart, as I have many fond memories of exchanging
      mathematical puzzles and solutions with my friends using this
      service.  Over time, the project grew quite popular on IRC
      networks, as well as in some schools and universities, where IRC
      users, learners and students used the service to share problems
      and solutions with one another, much as my friends and I had
      done in its early days.
    &lt;/p&gt;
    &lt;p&gt;
      I shut it down this year because I wanted to move on from the
      project.  Before the shutdown, a kind member of the
      &lt;a href=&quot;https://wiki.archiveteam.org/&quot;&gt;Archive Team&lt;/a&gt; worked
      with me to
      &lt;a href=&quot;https://web.archive.org/web/*/https://mathb.in/&quot;&gt;archive&lt;/a&gt;
      all posts from the now-defunct website.  Although shutting down
      this service was a bittersweet event for me, I feel relieved
      that I no longer have to run a live service in my spare time.
      While this was a good hobby ten years ago, it no longer is.  See
      my blog post
      &lt;a href=&quot;mathbin-is-shutting-down.html&quot;&gt;MathB.in Is Shutting
      Down&lt;/a&gt; for more details on the reasons behind this decision.
      The source code of this project remains open source and
      available at
      &lt;a href=&quot;https://github.com/susam/mathb&quot;&gt;github.com/susam/mathb&lt;/a&gt;.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;https://github.com/susam/quickqwerty&quot;&gt;QuickQWERTY&lt;/a&gt;:
      This is a touch-typing tutor that runs in a web browser.  I
      originally developed it in 2008 for myself and my friends.
      While I learnt touch typing on an actual typewriter as a child,
      those lessons did not stick with me.  Much later, while I was at
      university, I came across a Java applet-based touch-typing tutor
      that finally helped me learn touch typing properly.  I disliked
      installing Java plugins in the web browser, which is why I later
      developed this project in plain HTML and JavaScript.  This year,
      I carried out a major refactoring to collapse the entire project
      into a single standalone HTML file with no external
      dependencies.  The source code has been greatly simplified as
      well.
    &lt;/p&gt;
    &lt;p&gt;
      When I was younger and more naive, inspired by the complexity
      and multiple layers of abstraction I saw in popular open source
      and professional projects, I tended to introduce similar
      abstractions and complexity into my personal projects.  Over
      time, however, I began to appreciate simplicity.  The new code
      for this project is smaller and simpler.  I am quite happy with
      the end result.  You can take a look at the code here:
      &lt;a href=&quot;https://github.com/susam/quickqwerty/blob/main/quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.
      If you want to use the typing tutor, go here:
      &lt;a href=&quot;quickqwerty.html&quot;&gt;QuickQWERTY&lt;/a&gt;.  Unfortunately, it
      does not support keyboard layouts other than QWERTY.  When I
      originally developed this project, my view of the computing
      world was rather limited.  I was not even aware that other
      keyboard layouts existed.  You are, however, very welcome to
      fork the project and adapt the lessons for other layouts.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;https://github.com/susam/cfrs&quot;&gt;CFRS[]&lt;/a&gt;: This project
      was my first contribution to the quirky world of esolangs.
      CFRS[] is an extremely minimal drawing language consisting of
      only six simple commands: &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt;,
      &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;S&lt;/code&gt;, &lt;code&gt;[&lt;/code&gt; and
      &lt;code&gt;]&lt;/code&gt;.  I developed it in 2023 and have since been
      maintaining it with occasional bug fixes.  This year, I
      &lt;a href=&quot;https://github.com/susam/cfrs/commit/9bae21f&quot;&gt;fixed an
      annoying bug&lt;/a&gt; that caused the drawing canvas to overflow on
      some mobile web browsers.  A new demo also arrived from the
      community this year and has now been added to the community demo
      page.  See
      &lt;a href=&quot;https://susam.github.io/cfrs/demo.html#glimmering-galaxy&quot;&gt;Glimmering
      Galaxy&lt;/a&gt; for the new demo.  If you want to play with CFRS[]
      now, visit &lt;a href=&quot;cfrs.html&quot;&gt;CFRS[]&lt;/a&gt;.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;https://github.com/susam/fxyt&quot;&gt;FXYT&lt;/a&gt;: This is
      another esolang project of mine.  This too is a minimal drawing
      language, though not as minimal as CFRS[].  Instead, it is a
      stack-based, postfix canvas colouring language with only 36
      simple commands.  The canvas overflow bug described in the
      previous entry affected this project as well.  That has now been
      fixed.  Further, by popular demand, the maximum allowed code
      length has been increased from 256 bytes to 1024 bytes.  This
      means there is now more room for writing more complex FXYT
      programs.  Additionally, the maximum code length for
      distributable demo links has been increased from 64 bytes to 256
      bytes.  This allows several more impressive demos to have their
      own distributable links.  Visit &lt;a href=&quot;fxyt.html&quot;&gt;FXYT&lt;/a&gt; to
      try it out now.  See also the
      &lt;a href=&quot;https://susam.github.io/fxyt/demo.html&quot;&gt;Community Demos&lt;/a&gt; to
      view some fascinating artwork created by the community.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;https://github.com/susam/nq&quot;&gt;Nerd Quiz&lt;/a&gt;: This is a
      new project I created a couple of months ago.  It is a simple
      HTML tool that lets you test your nerdiness through short
      quizzes.  Each question is drawn from my everyday moments of
      reading, writing, thinking, learning and exploring.  The project
      is meant to serve as a repository of interesting facts I come
      across in daily life, captured in the form of quiz questions.
      Go here to try it out: &lt;a href=&quot;nq.html&quot;&gt;Nerd Quiz&lt;/a&gt;.  I hope
      you will enjoy these little bits of knowledge as much as I
      enjoyed discovering them.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;primegrid.html&quot;&gt;Prime Number Grid Explorer&lt;/a&gt;: This is
      a simple prime number grid visualiser I wrote for fun.  It plots
      the prime numbers in a grid where you can set the number of rows
      and the number of columns.  It uses the Miller-Rabin primality
      test with bases drawn from
      &lt;a href=&quot;https://oeis.org/A014233&quot;&gt;oeis.org/A014233&lt;/a&gt; to
      determine whether a number is prime.  This allows it to
      accurately test whether a number is prime up to
      3317044064679887385961980.  For example,
      &lt;a href=&quot;primegrid.html#3317044064679887385961781-20-10&quot;&gt;this
      grid&lt;/a&gt; shows the upper limit of the numbers this tool can
      check.  The three circles displayed there represent the prime
      numbers 3317044064679887385961783, 3317044064679887385961801 and
      3317044064679887385961813.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;https://github.com/susam/mvs&quot;&gt;Mark V. Shaney
      Junior&lt;/a&gt;: Finally, I have my own Markov gibberish generator.
      Always wanted to have one.  The project is inspired by the
      legendary Usenet bot named Mark V. Shaney that used to post
      messages to various newsgroups in the 1980s.  My Markov chain
      program is written in about 30 lines of Python.  I ran it on my
      24 years of blog posts consisting of over 200 posts and about
      200,000 words and it generated some pretty interesting
      gibberish.  See my blog post
      &lt;a href=&quot;fed-24-years-of-posts-to-markov-model.html&quot;&gt;Fed 24
      Years of My Posts to Markov Model&lt;/a&gt; to see the examples.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;elliptical-python-programming.html&quot;&gt;Elliptical Python
      Programming&lt;/a&gt;: If the previous item was not silly enough, this
      one surely is.  Earlier this year, I wrote a blog post
      describing the fine art of Python programming using copious
      amounts of ellipses.  I will not discuss it further here to
      avoid spoilers.  I&apos;ll just say that any day I&apos;m able to do
      something pointless, whimsical and fun with computers is a good
      day for me.  And it was a good day when I wrote this post.
      Please visit the link above to read the post.  I hope you find
      it fun.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;fizz-buzz-with-cosines.html&quot;&gt;Fizz Buzz with
      Cosines&lt;/a&gt;: Another silly post in which I explain how to
      compute the discrete Fourier transform of the Fizz Buzz sequence
      and derive a closed-form expression that can be used to print
      the sequence.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      &lt;a href=&quot;fizz-buzz-in-css.html&quot;&gt;Fizz Buzz in CSS&lt;/a&gt;: Yet
      another Fizz Buzz implementation, this time using just four
      lines of CSS.
    &lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  That wraps up my coding adventures for this year.  There were fewer
  hobby projects than usual but I enjoyed spending more time learning
  new things and revisiting old ones.  One long-running project came
  to an end, another was cleaned up and a few small new ideas appeared
  along the way.  Looking forward to what the next year brings.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code-2025.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/retrospective.html&quot;&gt;#retrospective&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Mark V. Shaney Junior 0.2.0</title>
    <link href="https://susam.net/code/news/mvs/0.2.0.html"/>
    <id>urn:uuid:e8c48bfa-5413-4d14-8129-748092bdabee</id>
    <updated>2025-12-14T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  Mark V. Shaney Junior 0.2.0 is the second release of this little
  Markov gibberish generator.  This release brings two small changes.
  First, it now reads the training data from standard input instead of
  a hardcoded file.  Second, the program filename has been changed
  from &lt;code&gt;mvs.py&lt;/code&gt; to &lt;code&gt;mvs&lt;/code&gt; to reflect that it is
  an executable file and can be run as &lt;code&gt;./mvs&lt;/code&gt; on most Unix
  and Linux systems.
&lt;/p&gt;
&lt;p&gt;
  The source and a detailed documentation for this project are
  available at &lt;a href=&quot;https://github.com/susam/mvs&quot;&gt;github.com/susam/mvs&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  See also
  &lt;a href=&quot;https://susam.net/fed-24-years-of-posts-to-markov-model.html&quot;&gt;this
  related blog post&lt;/a&gt; and a
  &lt;a href=&quot;https://news.ycombinator.com/item?id=46257607&quot;&gt;discussion
  on Hacker News&lt;/a&gt; about it.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/mvs/0.2.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/python.html&quot;&gt;#python&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Fed 24 Years of My Blog Posts to a Markov Model</title>
    <link href="https://susam.net/fed-24-years-of-posts-to-markov-model.html"/>
    <id>urn:uuid:4501f3b3-7e6c-4109-b15c-c38627658cff</id>
    <updated>2025-12-13T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  Yesterday I shared a little program called &lt;em&gt;Mark V. Shaney
  Junior&lt;/em&gt; at
  &lt;a href=&quot;https://github.com/susam/mvs&quot;&gt;github.com/susam/mvs&lt;/a&gt;.  It
  is a minimal implementation of a Markov text generator inspired by
  the legendary Mark V. Shaney program from the 1980s.  Mark V. Shaney
  was a synthetic Usenet user that posted messages to various
  newsgroups using text generated by a Markov model.  See the
  Wikipedia article
  &lt;a href=&quot;https://en.wikipedia.org/wiki/Mark_V._Shaney&quot;&gt;Mark
  V. Shaney&lt;/a&gt; for more details about it.  In this post, I will
  discuss my implementation of the model, explain how it works and
  share some of the results produced by it.
&lt;/p&gt;
&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#recreational-programming&quot;&gt;Recreational Programming&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gibberish&quot;&gt;Gibberish&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#markov-property&quot;&gt;The Markov Property&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#some-more-gibberish&quot;&gt;Some More Gibberish&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;recreational-programming&quot;&gt;Recreational Programming&lt;/h2&gt;
&lt;p&gt;
  The program I shared yesterday has only about
  &lt;a href=&quot;https://github.com/susam/mvs/blob/main/mvs&quot;&gt;30 lines of
  Python&lt;/a&gt; and favours simplicity over efficiency.  Even if you have
  never worked with Markov models before, as long as you know some
  Python programming, I am quite confident that it will take you less
  than 20 minutes to understand the whole program and make complete
  sense of it.  I also offer an explanation
  &lt;a href=&quot;#the-markov-property&quot;&gt;further below&lt;/a&gt; in this post.
&lt;/p&gt;
&lt;p&gt;
  As a hobby, I often engage in exploratory programming where I write
  computer programs not to solve a specific problem but simply to
  explore a particular idea or topic for the sole purpose of
  recreation.  I must have written small programs to explore Markov
  chains for various kinds of state spaces over a dozen times by now.
  Every time, I just pick my last experimental code and edit it to
  encode the new state space I am exploring.  That&apos;s usually my
  general approach to such one-off programs.  I have hundreds of tiny
  little experimental programs lying on my disk at any given time.
&lt;/p&gt;
&lt;p&gt;
  Once in a while, I get the itch to take one of those exploratory
  programs, give it some finishing touches, wrap it up in a nice Git
  repo along with a &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;CHANGES.md&lt;/code&gt;
  and the whole shebang and share it on
  &lt;a href=&quot;https://github.com/susam&quot;&gt;github.com/susam&lt;/a&gt; and
  &lt;a href=&quot;https://codeberg.org/susam&quot;&gt;codeberg.org/susam&lt;/a&gt;.  The
  Mark V. Shaney Junior program that I shared yesterday happened to be
  one such exercise.
&lt;/p&gt;
&lt;h2 id=&quot;gibberish&quot;&gt;Gibberish&lt;/h2&gt;
&lt;p&gt;
  If you scroll down the
  &lt;a href=&quot;https://github.com/susam/mvs#readme&quot;&gt;README&lt;/a&gt; of this
  project, you&apos;ll find some nice examples of the gibberish produced by
  this program.  The first few examples there are the result of
  training the model on &lt;em&gt;A Christmas Carol&lt;/em&gt; by Charles Dickens,
  one of my favourite authors.  It is often said that Dickens never
  used fewer words when more would suffice.  So I thought there
  couldn&apos;t be a better piece of text when it comes to testing out my
  tiny Markov model.  I&apos;ll not reproduce the generated text examples
  here for the sake of brevity.  If you are interested to take a look,
  just head over to the
  &lt;a href=&quot;https://github.com/susam/mvs#gibberish&quot;&gt;Gibberish&lt;/a&gt;
  section of the README.
&lt;/p&gt;
&lt;p&gt;
  Soon after sharing the project, I wondered what kind of gibberish it
  would produce if I fed all 24 years of my blog
  &lt;a href=&quot;pages.html&quot;&gt;posts and pages&lt;/a&gt; into the program.
  Well, here&apos;s one of the results:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;./mvs &amp;lt; susam.txt&lt;/kbd&gt;
while a query replace operation is approved by the user. The above
variable defines the build job. It can be incredibly useful while
working on assembly language and machine code. In fact, all internal
resources like the result to refine the search prompt changes from
bck-i-search: to fwd-i-search:. Now type C-SPC (i.e. ctrl+space) to
set a mark causes Emacs to use 32-bit registers like EBP, ESP,
etc. Thus the behaviour is undefined. Such code may behave differently
when compiled with the readily available GNU tools like the shape
of 8. Flipping &quot;P&quot; horizontally makes it a proper quine: cat $0&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  This is the text that comes out after the program consumes over 200
  posts consisting of about 200,000 words.  My blog also has
  a &lt;a href=&quot;comments/&quot;&gt;comments section&lt;/a&gt; with over 500
  comments consisting of about 40,000 words.  All comments were
  excluded while training the model.  Here is another output example:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;./mvs &amp;lt; susam.txt&lt;/kbd&gt;
enjoy asking &quot;what happens if&quot; and then type M-x zap-up-to-char RET
b. The buffer for this specific video, the actual fare for 8.3 km and
11 are all written from scratch. No prior knowledge is expected to
slow down in future. For now, I will add a statement like x =
0.999..., the inner corner square as discussed in the code segment
into the REPL window. Unlike Slimv, Vlime can work with and debug
executable files, it can be more convenient. M-x: Execute Extended
Command The key sequence M-q invokes the command cat and type TAB to
indent the current&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  Here is a particularly incoherent but amusing one:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;./mvs &amp;lt; susam.txt&lt;/kbd&gt;
Then open a new Lisp source file and the exact answer could harm
students&apos; self-esteem. Scientists have arbitrarily assumed that an
integral domain. However, the string and comment text. To demonstrate
how a build job can trigger itself, pass input to standard output or
standard error), Eshell automatically runs the following command in
Vim and Emacs will copy the message length limit of 512 characters,
etc. For example, while learning to play the game between normal mode
to move the point is on an old dictionary lying around our house and
that is moving to the small and supportive community&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  No, I have never said anywhere that opening a Lisp source file could
  harm anyone&apos;s self-esteem.  The text generator has picked up the
  &apos;Lisp source file&apos; phrase from my
  &lt;a href=&quot;lisp-in-vim.html&quot;&gt;Lisp in Vim&lt;/a&gt; post and the
  &apos;self-esteem&apos; bit from the &lt;a href=&quot;from-perl-to-pi.html&quot;&gt;From Perl
  to Pi&lt;/a&gt; post.
&lt;/p&gt;
&lt;h2 id=&quot;markov-property&quot;&gt;The Markov Property&lt;/h2&gt;
&lt;p&gt;
  By default, this program looks at trigrams (all sequences of three
  adjacent words) and creates a map where the first two words of the
  trigram are inserted as the key and the third word is appended to
  its list value.  This map is the model.  In this way, the model
  captures each pair of adjacent words along with the words that
  immediately follow each pair.  The text generator first chooses a
  key (a pair of words) at random and selects a word that follows.  If
  there are multiple followers, it picks one uniformly at random.  It
  then repeats this process with the most recent pair of words,
  consisting of one word from the previous pair and the word that was
  just picked.  It continues to do this until it can no longer find a
  follower or a fixed word limit (100 by default) is reached.  That is
  pretty much the whole algorithm.  There isn&apos;t much more to it.  It
  is as simple as it gets.  For that reason, I often describe a simple
  Markov model like this as the &apos;hello, world&apos; for language models.
&lt;/p&gt;
&lt;p&gt;
  If the same trigram occurs multiple times in the training data, the
  model records the follower word (the third word) multiple times in
  the list associated with the key (the first two words).  This
  representation can be optimised, of course, by keeping frequencies
  of the follower words rather than duplicating them in the list, but
  that is left as an exercise to the reader.  In any case, when the
  text generator chooses a follower for a given pair of words, a
  follower that occurs more frequently after that pair has a higher
  probability of being chosen.  In effect, the next word is sampled
  based only on the previous two words and not on the full history of
  the generated text.  This memoryless dependence on the current state
  is what makes the generator Markov.  Formally, for a discrete-time
  stochastic process, the Markov property can be expressed as

  \[
    P(X_{n+1} \mid X_n, X_{n-1}, \ldots, X_1) = P(X_{n+1} \mid X_n).
  \]

  where \( X_n \) represents the \( n \)th state.  In our case, each
  state \( X_n \) is a pair of words \( (w_{n-1}, w_{n}) \) but the
  state space could just as well consist of other objects, such as a
  pair of characters, pixel values or musical notes.  The sequence of
  states \( (X_1, X_2, \dots) \) visited by the program forms a Markov
  chain.  The left-hand side of the equation denotes the conditional
  distribution of the next state \( X_{n+1} \) given the entire
  history of states \( X_1, X_2, \dots, X_n, \) while the right-hand
  side conditions only on the current state \( X_n.  \)  When both are
  equal, it means that the probability of the next state depends only
  on the current state and not on the earlier states.  This is the
  Markov property.  It applies to the text generation process only,
  not the training data.  The training data is used only to estimate
  the transition probabilities of the model.
&lt;/p&gt;
&lt;h2 id=&quot;some-more-gibberish&quot;&gt;Some More Gibberish&lt;/h2&gt;
&lt;p&gt;
  In 2025, given the overwhelming popularity of large language models
  (LLMs), Markov models like this look unimpressive.  Unlike LLMs, a
  simple Markov model cannot capture global structure or long-range
  dependencies within the text.  It relies entirely on local word
  transition statistics.  Also, these days, one hardly needs a Markov
  model to generate gibberish; social media provides an ample supply.
  Nevertheless, I think the simplicity of its design and
  implementation serves as a good entry point into language models.
&lt;/p&gt;
&lt;p&gt;
  In my implementation, the number of words in the key of the map can
  be set via command line arguments.  By default, it is 2 as described
  above.  This value is also known as the order of the model.  So by
  default the order is 2.  If we increase it to, say, 3 or 4, the
  generated text becomes a little more coherent.  Here is one such
  example:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;./mvs 4 &amp;lt; susam.txt&lt;/kbd&gt;
It is also possible to search for channels by channel names. For
example, on Libera Chat, to search for all channels with &apos;python&apos; in
its name, enter the IRC command: /msg alis list python. Although I
have used Libera Chat in the examples above, there are plenty of
infinite fields, so they must all be integral domains too. Consider
the field of rational numbers Q. Another quick way to arrive at this
fact is to observe that when one knight is placed on a type D square,
only two positions for the second knight such that the two knights
attack&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  Except for a couple of abrupt and meaningless transitions, the text
  is mostly coherent.  We need to be careful about not increasing the
  order too much.  In fact, if we increase the order of the model to
  5, the generated text becomes very dry and factual because it begins
  to quote large portions of the blog posts verbatim.  Not much fun
  can be had with that.
&lt;/p&gt;
&lt;p&gt;
  Before I end this post, let me present one final example where I ask
  it to generate text from an initial prompt:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;./mvs 2 100 &apos;Finally we&apos; &amp;lt; susam.txt&lt;/kbd&gt;
Finally we divide this number by a feed aggregrator for Emacs-related
blogs. The following complete key sequences describe the effects of
previous evaluations shall have taken a simple and small to contain
bad content. This provides an interactive byte-compiled Lisp function
in MATLAB and GNU bash 5.1.4 on Debian is easily reproducible in
Windows XP. Older versions might be able to run that server for me it
played a significant burden on me as soon as possible. C-u F: Visit
the marked files or directories in the sense that it was already
initiated and we were to complete the proof.&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  Apparently, this is how I would sound if I ever took up speaking
  gibberish!
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/fed-24-years-of-posts-to-markov-model.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/python.html&quot;&gt;#python&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Mark V. Shaney Junior 0.1.0</title>
    <link href="https://susam.net/code/news/mvs/0.1.0.html"/>
    <id>urn:uuid:0c29c75e-7019-45d9-a4bf-8c397533b11a</id>
    <updated>2025-12-12T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  Mark V. Shaney Junior is a minimal implementation of a Markov
  gibberish generator inspired by the legendary Mark V. Shaney program
  from the 1980s.  Mark V. Shaney was a synthetic Usenet user in the
  1980s that posted messages to newsgroups using text generated by a
  Markov chain program.  See the Wikipedia
  article &lt;a href=&quot;https://en.wikipedia.org/wiki/Mark_V._Shaney&quot;&gt;Mark
  V. Shaney&lt;/a&gt; for more details.
&lt;/p&gt;
&lt;p&gt;
  This release introduces the program
  &lt;a href=&quot;https://github.com/susam/mvs/blob/main/mvs.py&quot;&gt;mvs.py&lt;/a&gt;
  that implements a similar Markov text generator.  It reads a text
  corpus of text from standard input, build an internal Markov model
  and then generate text using the model.
&lt;/p&gt;
&lt;p&gt;
  Please visit
  &lt;a href=&quot;https://github.com/susam/mvs&quot;&gt;github.com/susam/mvs&lt;/a&gt;
  for the source code and some output examples.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/mvs/0.1.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/python.html&quot;&gt;#python&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Fizz Buzz with Cosines</title>
    <link href="https://susam.net/fizz-buzz-with-cosines.html"/>
    <id>urn:uuid:91190abf-f9ce-4d98-82b9-7fabd6bbdb14</id>
    <updated>2025-11-20T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  Fizz Buzz is a counting game that has become oddly popular in the
  world of computer programming as a simple test of basic programming
  skills.  The rules of the game are straightforward.  Players say the
  numbers aloud in order beginning with one.  Whenever a number is
  divisible by 3, they say &apos;Fizz&apos; instead.  If it is divisible by 5,
  they say &apos;Buzz&apos;.  If it is divisible by both 3 and 5, the player
  says both &apos;Fizz&apos; and &apos;Buzz&apos;.  Here is a typical Python program that
  prints this sequence:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for n in range(1, 101):
    if n % 15 == 0:
        print(&apos;FizzBuzz&apos;)
    elif n % 3 == 0:
        print(&apos;Fizz&apos;)
    elif n % 5 == 0:
        print(&apos;Buzz&apos;)
    else:
        print(n)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Here is the output:
  &lt;a href=&quot;files/blog/fizz-buzz.txt&quot;&gt;fizz-buzz.txt&lt;/a&gt;.  Can we make
  the program more complicated?  The words &apos;Fizz&apos;, &apos;Buzz&apos; and
  &apos;FizzBuzz&apos; repeat in a periodic manner throughout the sequence.
  What else is periodic?  Trigonometric functions!  Perhaps we can use
  trigonometric functions to encode all four rules of the sequence in
  a single closed-form expression.  That is what we are going to
  explore in this article, for fun and no profit.
&lt;/p&gt;
&lt;p&gt;
  By the end, we will obtain a discrete Fourier series that can take
  any integer \( n \) and select the corresponding text to be printed.
  In fact, we will derive it using two different methods.  First, we
  will follow a long-winded but hopefully enjoyable approach that
  relies on a basic understanding of complex exponentiation, geometric
  series and trigonometric functions.  Then, we will obtain the same
  result through a direct application of the discrete Fourier
  transform.
&lt;/p&gt;
&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#definitions&quot;&gt;Definitions&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#symbol-functions&quot;&gt;Symbol Functions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#index-function&quot;&gt;Index Function&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fizz-buzz-sequence&quot;&gt;Fizz Buzz Sequence&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#from-indicator-functions-to-cosines&quot;&gt;From Indicator Functions to Cosines&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#indicator-functions&quot;&gt;Indicator Functions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#complex-exponentials&quot;&gt;Complex Exponentials&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#cosines&quot;&gt;Cosines&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dft&quot;&gt;Discrete Fourier Transform&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#one-period-of-fizz-buzz&quot;&gt;One Period of Fizz Buzz&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fourier-coefficients&quot;&gt;Fourier Coefficients&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#inverse-transform&quot;&gt;Inverse Transform&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;definitions&quot;&gt;Definitions&lt;/h2&gt;
&lt;p&gt;
  Before going any further, we establish a precise mathematical
  definition for the Fizz Buzz sequence.  We begin by introducing a
  few functions that will help us define the Fizz Buzz sequence later.
&lt;/p&gt;
&lt;h3 id=&quot;symbol-functions&quot;&gt;Symbol Functions&lt;/h3&gt;
&lt;p&gt;
  We define a set of four functions \( \{ s_0, s_1, s_2, s_3 \} \) for
  integers \( n \) by:

  \begin{align*}
    s_0(n) &amp;amp;= n, \\
    s_1(n) &amp;amp;= \mathtt{Fizz}, \\
    s_2(n) &amp;amp;= \mathtt{Buzz}, \\
    s_3(n) &amp;amp;= \mathtt{FizzBuzz}.
  \end{align*}

  We call these the symbol functions because they produce every term
  that appears in the Fizz Buzz sequence.  The symbol function \( s_0
  \) returns \( n \) itself.  The functions \( s_1, \) \( s_2 \) and
  \( s_3 \) are constant functions that always return the literal
  words \( \mathtt{Fizz}, \) \( \mathtt{Buzz} \) and \(
  \mathtt{FizzBuzz} \) respectively, no matter what the value of \( n
  \) is.
&lt;/p&gt;
&lt;h3 id=&quot;index-function&quot;&gt;Index Function&lt;/h3&gt;
&lt;p&gt;
  We define a function \( f(n) \) for integer \( n \) by

  \[
    f(n) = \begin{cases}
      1 &amp;amp; \text{if } 3 \mid n \text{ and } 5 \nmid n, \\
      2 &amp;amp; \text{if } 3 \nmid n \text{ and } 5 \mid n, \\
      3 &amp;amp; \text{if } 3 \mid n \text{ and } 5 \mid n, \\
      0 &amp;amp; \text{otherwise}.
    \end{cases}
  \]

  The notation \( m \mid n \) means that the integer \( m \) divides
  the integer \( n, \) i.e. \( n \) is a multiple of \( m.  \)
  Equivalently, there exists an integer \( c \) such that \( n = cm
 .  \)  Similarly, \( m \nmid n \) means that \( m \) does not divide
  \( n, \) i.e. \( n \) is not a multiple of \( m.  \)
&lt;/p&gt;
&lt;p&gt;
  This function covers all four conditions involved in choosing the \(
  n \)th item of the Fizz Buzz sequence.  As we will soon see, this
  function tells us which of the four symbol functions produces the \(
  n \)th item of the Fizz Buzz sequence.  For this reason, we call \(
  f(n) \) the index function.
&lt;/p&gt;
&lt;h3 id=&quot;fizz-buzz-sequence&quot;&gt;Fizz Buzz Sequence&lt;/h3&gt;
&lt;p&gt;
  We now define the Fizz Buzz sequence as the sequence

  \[
    (s_{f(n)}(n))_{n = 1}^{\infty}
  \]

  We can expand the first few terms of the sequence explicitly as
  follows:

  \begin{align*}
    (s_{f(n)}(n))_{n = 1}^{\infty}
    &amp;amp;= (s_{f(1)}(1), \; s_{f(2)}(2), \; s_{f(3)}(3), \; s_{f(4)}(4), \;
            s_{f(5)}(5), \; s_{f(6)}(6), \; s_{f(7)}(7), \; \dots) \\
    &amp;amp;= (s_0(1), \; s_0(2), \; s_1(3), \; s_0(4),
            s_2(5), \; s_1(6), \; s_0(7), \; \dots) \\
    &amp;amp;= (1, \; 2, \; \mathtt{Fizz}, \; 4, \;
            \mathtt{Buzz}, \; \mathtt{Fizz}, \; 7, \; \dots).
  \end{align*}

  Note how the function \( f(n) \) produces an index \( i \) which we
  then use to select the symbol function \( s_i(n) \) to produce the
  \( n \)th term of the sequence.  This is precisely why we decided to
  call \( f(n) \) the index function while defining it in the previous
  section.
&lt;/p&gt;
&lt;h2 id=&quot;from-indicator-functions-to-cosines&quot;&gt;From Indicator Functions to Cosines&lt;/h2&gt;
&lt;p&gt;
  Here we discuss the first method of deriving our closed form
  expression, starting with indicator functions and rewriting them
  using complex exponentials and cosines.
&lt;/p&gt;
&lt;h3 id=&quot;indicator-functions&quot;&gt;Indicator Functions&lt;/h3&gt;
&lt;p&gt;
  Here is the index function \( f(n) \) from the previous section with
  its cases and conditions rearranged to make it easier to spot
  interesting patterns:

  \[
    f(n) = \begin{cases}
      0 &amp;amp; \text{if } 5 \nmid n \text{ and } 3 \nmid n, \\
      1 &amp;amp; \text{if } 5 \nmid n \text{ and } 3 \mid n, \\
      2 &amp;amp; \text{if } 5 \mid n \text{ and } 3 \nmid n, \\
      3 &amp;amp; \text{if } 5 \mid n \text{ and } 3 \mid n.
    \end{cases}
  \]

  This function helps us select another function \( s_{f(n)}(n) \)
  which in turn determines the \( n \)th term of the Fizz Buzz
  sequence.  Our goal now is to replace this piecewise formula with a
  single closed-form expression.  To do so, we first define indicator
  functions \( I_m(n) \) as follows:

  \[
    I_m(n) = \begin{cases}
      1 &amp;amp; \text{if } m \mid n, \\
      0 &amp;amp; \text{if } m \nmid n.
    \end{cases}
  \]

  The formula for \( f(n) \) can now be written as:

  \[
    f(n) = \begin{cases}
      0 &amp;amp; \text{if } I_5(n) = 0 \text{ and } I_3(n) = 0, \\
      1 &amp;amp; \text{if } I_5(n) = 0 \text{ and } I_3(n) = 1, \\
      2 &amp;amp; \text{if } I_5(n) = 1 \text{ and } I_3(n) = 0, \\
      3 &amp;amp; \text{if } I_5(n) = 1 \text{ and } I_3(n) = 1.
    \end{cases}
  \]

  Do you see a pattern?  Here is the same function written as a table:
&lt;/p&gt;
&lt;table class=&quot;grid center textcenter&quot;&gt;
  &lt;tr&gt;
    &lt;th&gt;\( I_5(n) \)&lt;/th&gt;
    &lt;th&gt;\( I_3(n) \)&lt;/th&gt;
    &lt;th&gt;\( f(n) \)&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;\( 0 \)&lt;/td&gt;
    &lt;td&gt;\( 0 \)&lt;/td&gt;
    &lt;td&gt;\( 0 \)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;\( 0 \)&lt;/td&gt;
    &lt;td&gt;\( 1 \)&lt;/td&gt;
    &lt;td&gt;\( 1 \)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;\( 1 \)&lt;/td&gt;
    &lt;td&gt;\( 0 \)&lt;/td&gt;
    &lt;td&gt;\( 2 \)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;\( 1 \)&lt;/td&gt;
    &lt;td&gt;\( 1 \)&lt;/td&gt;
    &lt;td&gt;\( 3 \)&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
  Do you see it now?  If we treat the values in the first two columns
  as binary digits and the values in the third column as decimal
  numbers, then in each row the first two columns give the binary
  representation of the number in the third column.  For example, \(
  3_{10} = 11_2 \) and indeed in the last row of the table, we see the
  bits \( 1 \) and \( 1 \) in the first two columns and the number \(
  3 \) in the last column.  In other words, writing the binary digits
  \( I_5(n) \) and \( I_3(n) \) side by side gives us the binary
  representation of \( f(n).  \)  Therefore

  \[
    f(n) = 2 \, I_5(n) + I_3(n).
  \]

  We can now write a small program to demonstrate this formula:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for n in range(1, 101):
    s = [n, &apos;Fizz&apos;, &apos;Buzz&apos;, &apos;FizzBuzz&apos;]
    i = (n % 3 == 0) + 2 * (n % 5 == 0)
    print(s[i])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  We can make it even shorter at the cost of some clarity:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for n in range(1, 101):
    print([n, &apos;Fizz&apos;, &apos;Buzz&apos;, &apos;FizzBuzz&apos;][(n % 3 == 0) + 2 * (n % 5 == 0)])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  What we have obtained so far is pretty good.  While there is no
  universal definition of a closed-form expression, I think most
  people would agree that the indicator functions as defined above are
  simple enough to be permitted in a closed-form expression.
&lt;/p&gt;
&lt;h3 id=&quot;complex-exponentials&quot;&gt;Complex Exponentials&lt;/h3&gt;
&lt;p&gt;
  In the previous section, we obtained the formula

  \[
    f(n) = I_3(n) + 2 \, I_5(n)
  \]

  which we then used as an index to look up the text to be printed.
  We also argued that this is a pretty good closed-form expression
  already.
&lt;/p&gt;
&lt;p&gt;
  However, in the interest of making things more complicated, we must
  ask ourselves: What if we are not allowed to use the indicator
  functions?  What if we must adhere to the commonly accepted meaning
  of a closed-form expression which allows only finite combinations of
  basic operations such as addition, subtraction, multiplication,
  division, integer exponents and roots with integer index as well as
  functions such as exponentials, logarithms and trigonometric
  functions.  It turns out that the above formula can be rewritten
  using only addition, multiplication, division and the cosine
  function.  Let us begin the translation.  Consider the sum

  \[
    S_m(n) = \sum_{k = 0}^{m - 1} e^{i 2 \pi k n / m},
  \]

  where \( i \) is the imaginary unit and \( n \) and \( m \) are
  integers.  This is a geometric series in the complex plane with
  ratio \( r = e^{i 2 \pi n / m}.  \)  If \( n \) is a multiple of \( m
 , \) then \( n = cm \) for some integer \( c \) and we get

  \[
    r
    = e^{i 2 \pi n / m}
    = e^{i 2 \pi c}
    = 1.
  \]

  Therefore, when \( n \) is a multiple of \( m, \) we get

  \[
    S_m(n)
    = \sum_{k = 0}^{m - 1} e^{i 2 \pi k n / m}
    = \sum_{k = 0}^{m - 1} 1^k
    = m.
  \]

  If \( n \) is not a multiple of \( m, \) then \( r \ne 1 \) and the
  geometric series becomes

  \[
    S_m(n)
    = \frac{r^m - 1}{r - 1}
    = \frac{e^{i 2 \pi n} - 1}{e^{i 2 \pi n / m} - 1}
    = 0.
  \]

  Therefore,

  \[
    S_m(n) = \begin{cases}
      m &amp;amp; \text{if } m \mid n, \\
      0 &amp;amp; \text{if } m \nmid n.
    \end{cases}
  \]

  Dividing both sides by \( m, \) we get

  \[
    \frac{S_m(n)}{m} = \begin{cases}
      1 &amp;amp; \text{if } m \mid n, \\
      0 &amp;amp; \text{if } m \nmid n.
    \end{cases}
  \]

  But the right-hand side is \( I_m(n).  \)  Therefore

  \[
    I_m(n)
    = \frac{S_m(n)}{m}
    = \frac{1}{m} \sum_{k = 0}^{m - 1} e^{i 2 \pi k n / m}.
  \]
&lt;/p&gt;
&lt;h3 id=&quot;cosines&quot;&gt;Cosines&lt;/h3&gt;
&lt;p&gt;
  We begin with Euler&apos;s formula

  \[
    e^{i x} = \cos x + i \sin x
  \]

  where \( x \) is a real number.  From this formula, we get

  \[
    e^{i x} + e^{-i x} = 2 \cos x.
  \]

  Therefore

  \begin{align*}
    I_3(n)
    &amp;amp;= \frac{1}{3} \sum_{k = 0}^2 e^{i 2 \pi k n / 3} \\
    &amp;amp;= \frac{1}{3} \left( 1 + e^{i 2 \pi n / 3} +
                                  e^{i 4 \pi n / 3} \right) \\
    &amp;amp;= \frac{1}{3} \left( 1 + e^{i 2 \pi n / 3} +
                                  e^{-i 2 \pi n / 3} \right) \\
    &amp;amp;= \frac{1}{3} + \frac{2}{3} \cos \left( \frac{2 \pi n}{3} \right).
  \end{align*}

  The third equality above follows from the fact that \( e^{i 4 \pi n
  / 3} = e^{i 6 \pi n / 3} e^{-i 2 \pi n / 3} = e^{i 2 \pi n} e^{-i 2
  \pi n/3} = e^{-i 2 \pi n / 3} \) when \( n \) is an integer.
&lt;/p&gt;
&lt;p&gt;
  The function above is defined for integer values of \( n \) but we
  can extend its formula to real \( x \) and plot it to observe its
  shape between integers.  As expected, the function takes the value
  \( 1 \) whenever \( x \) is an integer multiple of \( 3 \) and \( 0
  \) whenever \( x \) is an integer not divisible by \( 3.  \)
&lt;/p&gt;
&lt;figure class=&quot;soft&quot;&gt;
  &lt;img src=&quot;files/blog/fizz-buzz-i3.png&quot; alt=&quot;Graph&quot;&gt;
  &lt;figcaption&gt;
    Graph of \( \frac{1}{3} + \frac{2}{3} \cos \left( \frac{2 \pi x}{3} \right) \)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
  Similarly,

  \begin{align*}
    I_5(n)
    &amp;amp;= \frac{1}{5} \sum_{k = 0}^4 e^{i 2 \pi k n / 5} \\
    &amp;amp;= \frac{1}{5} \left( 1 + e^{i 2 \pi n / 5}
                                + e^{i 4 \pi n / 5}
                                + e^{i 6 \pi n / 5}
                                + e^{i 8 \pi n / 5} \right) \\
    &amp;amp;= \frac{1}{5} \left( 1 + e^{i 2 \pi n / 5}
                                + e^{i 4 \pi n / 5}
                                + e^{-i 4 \pi n / 5}
                                + e^{-i 2 \pi n / 5} \right) \\
    &amp;amp;= \frac{1}{5} + \frac{2}{5} \cos \left( \frac{2 \pi n}{5} \right)
                       + \frac{2}{5} \cos \left( \frac{4 \pi n}{5} \right).
  \end{align*}

  Extending this expression to real values of \( x \) allows us to
  plot its shape as well.  Once again, the function takes the value \(
  1 \) at integer multiples of \( 5 \) and \( 0 \) at integers not
  divisible by \( 5.  \)
&lt;/p&gt;
&lt;figure class=&quot;soft&quot;&gt;
  &lt;img src=&quot;files/blog/fizz-buzz-i5.png&quot; alt=&quot;Graph&quot;&gt;
  &lt;figcaption&gt;
    Graph of \(
      \frac{1}{5}
      + \frac{2}{5} \cos \left( \frac{2 \pi x}{5} \right)
      + \frac{2}{5} \cos \left( \frac{4 \pi x}{5} \right)
    \)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
  Recall that we expressed \( f(n) \) as

  \[
    f(n) = I_3(n) + 2 \, I_5(n).
  \]

  Substituting these trigonometric expressions yields

  \[
    f(n)
    = \frac{1}{3}
      + \frac{2}{3} \cos \left( \frac{2 \pi n}{3} \right)
      + 2 \cdot \left(
        \frac{1}{5}
        + \frac{2}{5} \cos \left( \frac{2 \pi n}{5} \right)
        + \frac{2}{5} \cos \left( \frac{4 \pi n}{5} \right)
      \right).
  \]

  A straightforward simplification gives

  \[
    f(n)
    = \frac{11}{15}
      + \frac{2}{3} \cos \left( \frac{2 \pi n}{3} \right)
      + \frac{4}{5} \cos \left( \frac{2 \pi n}{5} \right)
      + \frac{4}{5} \cos \left( \frac{4 \pi n}{5} \right).
  \]

  We can extend this expression to real \( x \) and plot it as well.
  The resulting curve takes the values \( 0, 1, 2 \) and \( 3 \) at
  integer points, as desired.
&lt;/p&gt;
&lt;figure class=&quot;soft&quot;&gt;
  &lt;img src=&quot;files/blog/fizz-buzz-f.png&quot; alt=&quot;Graph&quot;&gt;
  &lt;figcaption&gt;
    Graph of \(
      \frac{11}{15} +
      \frac{2}{3} \cos \left( \frac{2 \pi x}{3} \right) +
      \frac{4}{5} \cos \left( \frac{2 \pi x}{5} \right) +
      \frac{4}{5} \cos \left( \frac{4 \pi x}{5} \right)
    \)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
  Now we can write our Python program as follows:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from math import cos, pi
for n in range(1, 101):
    s = [n, &apos;Fizz&apos;, &apos;Buzz&apos;, &apos;FizzBuzz&apos;]
    i = round(11 / 15 + (2 / 3) * cos(2 * pi * n / 3)
                      + (4 / 5) * cos(2 * pi * n / 5)
                      + (4 / 5) * cos(4 * pi * n / 5))
    print(s[i])&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;dft&quot;&gt;Discrete Fourier Transform&lt;/h2&gt;
&lt;p&gt;
  The keen-eyed might notice that the expression we obtained for \(
  f(n) \) is a discrete Fourier series.  This is not surprising, since
  the output of a Fizz Buzz program depends only on \( n \bmod 15.  \)
  Any function on a finite cyclic group can be written exactly as a
  finite Fourier expansion.  In this section, we obtain \( f(n) \)
  using the discrete Fourier transform.  It is worth mentioning that
  the calculations presented here are quite tedious to do by hand.
  Nevertheless, this section offers a glimpse of how such calculations
  are performed.  By the end, we will arrive at exactly the same \(
  f(n) \) as before.  There is nothing new to discover here.  We
  simply obtain the same result by a more direct but more laborious
  method.  If this doesn&apos;t sound interesting, you may safely skip the
  subsections that follow.
&lt;/p&gt;
&lt;h3 id=&quot;one-period-of-fizz-buzz&quot;&gt;One Period of Fizz Buzz&lt;/h3&gt;
&lt;div style=&quot;display: none&quot;&gt;\( \gdef\arraystretch{1.2} \)&lt;/div&gt;
&lt;p&gt;
  We know that \( f(n) \) is a periodic function with period \( 15.  \)
  To apply the discrete Fourier transform, we look at one complete
  period of the function using the values \( n = 0, 1, \dots, 14.  \)
  Over this period, we have:

  \begin{array}{c|ccccccccccccccc}
      n &amp;amp;  0 &amp;amp;  1 &amp;amp;  2 &amp;amp;  3 &amp;amp;  4
        &amp;amp;  5 &amp;amp;  6 &amp;amp;  7 &amp;amp;  8 &amp;amp;  9
        &amp;amp; 10 &amp;amp; 11 &amp;amp; 12 &amp;amp; 13 &amp;amp; 14 \\
    \hline
    f(n) &amp;amp; 3 &amp;amp;  0 &amp;amp;  0 &amp;amp;  1 &amp;amp;  0
         &amp;amp; 2 &amp;amp;  1 &amp;amp;  0 &amp;amp;  0 &amp;amp;  1
         &amp;amp; 2 &amp;amp;  0 &amp;amp;  1 &amp;amp;  0 &amp;amp;  0
  \end{array}

  The discrete Fourier series of \( f(n) \) is

  \[
    f(n) = \sum_{k = 0}^{14} c_k \, e^{i 2 \pi k n / 15}
  \]

  where the Fourier coefficients \( c_k \) are given by the discrete
  Fourier transform

  \[
    c_k = \frac{1}{15} \sum_{n = 0}^{14} f(n) e^{-i 2 \pi k n / 15}.
  \]

  for \( k = 0, 1, \dots, 14.  \)  The formula for \( c_k \) is called
  the discrete Fourier transform (DFT).  The formula for \( f(n) \) is
  called the inverse discrete Fourier transform (IDFT).
&lt;/p&gt;
&lt;h3 id=&quot;fourier-coefficients&quot;&gt;Fourier Coefficients&lt;/h3&gt;
&lt;p&gt;
  Let \( \omega = e^{-i 2 \pi / 15}.  \)  Then using the values of \(
  f(n) \) from the table above, the DFT becomes:

  \[
    c_k = \frac{3 + \omega^{3k} + 2 \omega^{5k} + \omega^{6k}
                  + \omega^{9k} + 2 \omega^{10k} + \omega^{12k}}{15}.
  \]

  Substituting \( k = 0, 1, 2, \dots, 14 \) into the above equation
  gives us the following Fourier coefficients:

  \begin{align*}
    c_{0}  &amp;amp;= \frac{11}{15}, \\
    c_{3}  &amp;amp;= c_{6} = c_{9} = c_{12} = \frac{2}{5}, \\
    c_{5}  &amp;amp;= c_{10} = \frac{1}{3}, \\
    c_{1}  &amp;amp;= c_{2} = c_{4} = c_{7} = c_{8} = c_{11} = c_{13} = c_{14} = 0.
  \end{align*}

  Calculating these Fourier coefficients by hand can be rather
  tedious.  In practice they are almost always calculated using
  numerical software, computer algebra systems or even simple code
  such as the example here:
  &lt;a href=&quot;code/fizz-buzz-fourier/fizz-buzz-fourier.py&quot;&gt;fizz-buzz-fourier.py&lt;/a&gt;.
&lt;/p&gt;
&lt;h3 id=&quot;inverse-transform&quot;&gt;Inverse Transform&lt;/h3&gt;
&lt;p&gt;
  Once the coefficients are known, we can substitute them into the
  inverse transform introduced earlier to obtain

  \begin{align*}
    f(n)
    &amp;amp;= \sum_{k = 0}^{14} c_k \, e^{i 2 \pi k n / 15} \\[1.5em]
    &amp;amp;= \frac{11}{15}
           + \frac{2}{5} \left(
             e^{i 2 \pi \cdot 3n / 15}
             + e^{i 2 \pi \cdot 6n / 15}
             + e^{i 2 \pi \cdot 9n / 15}
             + e^{i 2 \pi \cdot 12n / 15}
           \right) \\
           &amp;amp; \phantom{=\frac{11}{15}}
           + \frac{1}{3} \left(
             e^{i 2 \pi \cdot 5n / 15}
             + e^{i 2 \pi \cdot 10n / 15}
           \right) \\[1em]
    &amp;amp;= \frac{11}{15}
           + \frac{2}{5} \left(
             e^{i 2 \pi \cdot 3n / 15}
             + e^{i 2 \pi \cdot 6n / 15}
             + e^{-i 2 \pi \cdot 6n / 15}
             + e^{-i 2 \pi \cdot 3n / 15}
           \right) \\
           &amp;amp; \phantom{=\frac{11}{15}}
           + \frac{1}{3} \left(
             e^{i 2 \pi \cdot 5n / 15}
             + e^{-i 2 \pi \cdot 5n / 15}
           \right) \\[1em]
    &amp;amp;= \frac{11}{15}
       + \frac{2}{5} \left(
         2 \cos \left( \frac{2 \pi n}{5} \right)
         + 2 \cos \left( \frac{4 \pi n}{5} \right)
       \right) \\
       &amp;amp; \phantom{=\frac{11}{15}}
       + \frac{1}{3} \left(
         2 \cos \left( \frac{2 \pi n}{3} \right)
       \right) \\[1em]
    &amp;amp;= \frac{11}{15} +
       \frac{4}{5} \cos \left( \frac{2 \pi n}{5} \right) +
       \frac{4}{5} \cos \left( \frac{4 \pi n}{5} \right) +
       \frac{2}{3} \cos \left( \frac{2 \pi n}{3} \right).
  \end{align*}

  This is exactly the same expression for \( f(n) \) we obtained in
  the previous section.  We see that the Fizz Buzz index function \(
  f(n) \) can be expressed precisely using the machinery of Fourier
  analysis.
&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
  To summarise, we have defined the Fizz Buzz sequence as

  \[
    (s_{f(n)}(n))_{n = 1}^{\infty}
  \]

  where

  \[
    f(n)
    = \frac{11}{15} +
      \frac{2}{3} \cos \left( \frac{2 \pi n}{3} \right) +
      \frac{4}{5} \cos \left( \frac{2 \pi n}{5} \right) +
      \frac{4}{5} \cos \left( \frac{4 \pi n}{5} \right).
  \]

  and \( s_0(n) = n, \) \( s_1(n) = \mathtt{Fizz}, \) \( s_2(n) =
  \mathtt{Buzz} \) and \( s_3(n) = \mathtt{FizzBuzz}.  \)  A Python
  program to print the Fizz Buzz sequence based on this definition was
  presented earlier.  That program can be written more succinctly as
  follows:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from math import cos, pi
for n in range(1, 101):
    print([n, &apos;Fizz&apos;, &apos;Buzz&apos;, &apos;FizzBuzz&apos;][round(11 / 15 + (2 / 3) * cos(2 * pi * n / 3) + (4 / 5) * (cos(2 * pi * n / 5) + cos(4 * pi * n / 5)))])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  We can also wrap this up nicely in a shell one-liner, in case you
  want to share it with your friends and family and surprise them:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -c &apos;from math import cos, pi; [print([n, &quot;Fizz&quot;, &quot;Buzz&quot;, &quot;FizzBuzz&quot;][round(11/15 + (2/3) * cos(2*pi*n/3) + (4/5) * (cos(2*pi*n/5) + cos(4*pi*n/5)))]) for n in range(1, 101)]&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  We have taken a simple counting game and turned it into a
  trigonometric construction consisting of a discrete Fourier series
  with three cosine terms and four coefficients.  None of this makes
  Fizz Buzz any easier.  Quite the contrary.  But it does show that
  every \( \mathtt{Fizz} \) and \( \mathtt{Buzz} \) now owes its
  existence to a particular set of Fourier coefficients.  We began
  with the modest goal of making this simple problem more complicated.
  I think it is safe to say that we did not fall short.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/fizz-buzz-with-cosines.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/absurd.html&quot;&gt;#absurd&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/python.html&quot;&gt;#python&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/mathematics.html&quot;&gt;#mathematics&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/puzzle.html&quot;&gt;#puzzle&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>QuickQWERTY 1.2.0</title>
    <link href="https://susam.net/code/news/quickqwerty/1.2.0.html"/>
    <id>urn:uuid:c1e3f867-b9cd-44aa-82ac-1fe0aff37ce3</id>
    <updated>2025-10-05T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  QuickQWERTY 1.2.0 is now available.  QuickQWERTY is a web-based
  touch typing tutor for QWERTY keyboards that runs directly in the
  web browser.
&lt;/p&gt;
&lt;p&gt;
  This release brings small improvements to make the typing tutor
  smoother and the lessons more accurate.  When you return to the
  application, it no longer needlessly redirects you back to Unit 1.1
  if that was the last lesson you practised.
&lt;/p&gt;
&lt;p&gt;
  Several lessons have received minor corrections.  Units 7.2 and 7.3
  no longer include the word &quot;tyre&quot;, avoiding a preference for either
  British or American spelling.  Units 9.2 and 9.3 now spell
  &quot;orwellian&quot; correctly.  A factual error in Unit 17.4 has been fixed
  too.  It previously said &quot;89 is one more than 99&quot;, which now
  correctly reads &quot;89 is one more than 88&quot;.
&lt;/p&gt;
&lt;p&gt;
  Finally, switching between the 5-6 and 6-7 split layouts is now more
  reliable.  In the previous release, pressing &lt;kbd&gt;Esc&lt;/kbd&gt; to
  cancel the confirmation dialogue still caused the switch to go
  through.  Now pressing &lt;kbd&gt;Esc&lt;/kbd&gt; properly cancels the switch as
  expected.
&lt;/p&gt;
&lt;p&gt;
  To try out the new release of QuickQWERTY, go to
  &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/quickqwerty/1.2.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>QuickQWERTY 1.1.0</title>
    <link href="https://susam.net/code/news/quickqwerty/1.1.0.html"/>
    <id>urn:uuid:1444843c-0d17-44ec-8fcf-b88e4aa3f132</id>
    <updated>2025-09-28T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  There has been a new release of QuickQWERTY after over 10 years!
  QuickQWERTY is a web-based touch typing tutor for QWERTY keyboards
  that runs directly in the web browser.  You can try it out
  here: &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  The new release, version 1.1.0 of QuickQWERTY, introduces a new
  input command named &lt;code&gt;reset&lt;/code&gt;, which is a synoymn for the
  existing commands &lt;code&gt;restart&lt;/code&gt; and &lt;code&gt;rst&lt;/code&gt; used to
  restart the current lesson.  Another input command
  named &lt;code&gt;test&lt;/code&gt; runs an internal suite of tests to validate
  that no lesson involves keys that have not been introduced in the
  current or prior lessons.  Further, in this release, the application
  has been fully rewritten as a single standalone HTML file with no
  external dependencies and the source code has been greatly
  simplified.
&lt;/p&gt;
&lt;p&gt;
  There are a number of other changes too.  Words per minute (WPM)
  calculation has been refined so the first character, which starts
  the timer, is not counted.  The guides have been enhanced.  Keys to
  be pressed are now highlighted more clearly and reminders to return
  fingers to their home positions have been added to each guide.  The
  confirmation dialogue for switching between 6-7 split and 5-6 split
  styles now uses the HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element,
  replacing the dated &lt;code&gt;window.confirm()&lt;/code&gt; prompt.  In
  addition, when the application is opened without a fragment
  identifier in the URL, the most recently practised lesson loads
  automatically.
&lt;/p&gt;
&lt;p&gt;
  The user interface has been simplified.  The previous and next links
  (&lt;code&gt;&amp;laquo;&lt;/code&gt; and &lt;code&gt;&amp;raquo;&lt;/code&gt;) for navigating
  lessons have been removed, as they added little value and made the
  layout of the various user interface components more complex.  The
  &quot;Next lesson&quot; advice link remains for moving to the next lesson when
  the current one is completed successfully.  Tooltips showing
  additional typing metrics have also been removed due to limited
  usefulness.
&lt;/p&gt;
&lt;p&gt;
  The lessons have been updated to remove spellings specific to
  American English.  Care has been taken to use only words that are
  spelled the same in American and British English with one exception.
  The word &quot;tyre&quot;, in its British English spelling, was mistakenly
  left in the lesson.  That will be fixed in the next release.
&lt;/p&gt;
&lt;p&gt;
  Finally, QuickQWERTY&apos;s licence has changed from the BSD-2-Clause
  licence to the MIT licence.  The source code is available at
  &lt;a href=&quot;https://github.com/susam/quickqwerty&quot;&gt;github.com/susam/quickqwerty&lt;/a&gt;.
  To run this release and learn touch typing on a QWERTY keyboard, go
  to &lt;a href=&quot;../../../quickqwerty.html&quot;&gt;quickqwerty.html&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/quickqwerty/1.1.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>My Lobsters Interview</title>
    <link href="https://susam.net/my-lobsters-interview.html"/>
    <id>urn:uuid:0e68be1e-5da3-4ccf-be8f-2a1d978aa5d8</id>
    <updated>2025-09-12T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  I recently had an engaging conversation with Alex
  (&lt;a href=&quot;https://lobste.rs/~veqq&quot;&gt;@veqq&lt;/a&gt;) from the
  &lt;a href=&quot;https://lobste.rs/&quot;&gt;Lobsters&lt;/a&gt; community about computing,
  mathematics and a range of related topics.  Our conversation was
  later published on the community website as
  &lt;a href=&quot;https://lobste.rs/s/kltoas&quot;&gt;Lobsters Interview with
  Susam&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  I should mention the sections presented in that post are not in the
  same order in which we originally discussed them.  The sections were
  edited and rearranged by Alex to improve the flow and avoid
  repetition of similar topics too close to each other.
&lt;/p&gt;
&lt;p&gt;
  This page preserves a copy of our discussion as edited by Alex, so I
  can keep an archived version on my website.  In my copy, I have
  added a table of contents to make it easier to navigate to specific
  sections.  The interview itself follows the table of contents.  I
  hope you enjoy reading it.
&lt;/p&gt;
&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#lisp-and-other-things&quot;&gt;Lisp and Other Things&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#lisp-emacs-and-mathematics&quot;&gt;Lisp, Emacs and Mathematics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#interests-and-exploration&quot;&gt;Interests and Exploration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#computing-for-fun&quot;&gt;Computing for Fun&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#computing-activities&quot;&gt;Computing Activities&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#programming-vs-domains&quot;&gt;Programming vs Domains&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#old-functionality-and-new-problems&quot;&gt;Old Functionality and New Problems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#designing-for-composability&quot;&gt;Designing for Composability&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#small-vs-large-functions&quot;&gt;Small vs Large Functions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#domains-and-projects&quot;&gt;Domains and Projects&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#double-spacing-and-touch-typing&quot;&gt;Double Spacing and Touch Typing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#approach-to-learning&quot;&gt;Approach to Learning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#managing-time-and-distractions&quot;&gt;Managing Time and Distractions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#blogging&quot;&gt;Blogging&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#forums&quot;&gt;Forums&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mathb-moderation-problems&quot;&gt;MathB Moderation Problems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#favourite-mathematics-textbooks&quot;&gt;Favourite Mathematics Textbooks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mathematics-and-computing&quot;&gt;Mathematics and Computing&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conversation&quot;&gt;Our Conversation&lt;/h2&gt;
&lt;!-- Lisp and other things --&gt;
&lt;p class=&quot;question&quot; id=&quot;lisp-and-other-things&quot;&gt;
  Hi &lt;a href=&quot;https://lobste.rs/~susam&quot;&gt;@susam&lt;/a&gt;, I primarily know
  you as a Lisper, what other things do you use?
&lt;/p&gt;
&lt;p&gt;
  Yes, I use Lisp extensively for my personal projects and much of
  what I do in my leisure is built on it.  I ran
  a &lt;a href=&quot;https://github.com/susam/mathb&quot;&gt;mathematics pastebin&lt;/a&gt;
  for close to thirteen years.  It was quite popular on some IRC
  channels.  The pastebin was written in Common Lisp.
  My &lt;a href=&quot;https://susam.net/&quot;&gt;personal website&lt;/a&gt; and blog are
  generated using a tiny static site generator written in Common Lisp.
  Over the years I have built several other personal tools in it as
  well.
&lt;/p&gt;
&lt;p&gt;
  I am an active Emacs Lisp programmer too.  Many of my software tools
  are in fact Emacs Lisp functions that I invoke with convenient key
  sequences.  They help me automate repetitive tasks as well as
  improve my text editing and task management experience.
&lt;/p&gt;
&lt;p&gt;
  I use plenty of other tools as well.  In my early adulthood, I spent
  many years working with C, C++, Java and PHP.  My
  &lt;a href=&quot;https://issues.apache.org/jira/browse/NUTCH-559&quot;&gt;first
  substantial open source contribution&lt;/a&gt; was to the Apache Nutch
  project which was in Java and one of my early original open source
  projects was &lt;a href=&quot;https://github.com/susam/uncap&quot;&gt;Uncap&lt;/a&gt;, a C
  program to remap keys on Windows.
&lt;/p&gt;
&lt;p&gt;
  These days I use a lot of Python, along with some Go and Rust, but
  Lisp remains important to my personal work.  I also enjoy writing
  small standalone tools directly in HTML and JavaScript, often with
  all the code in a single file in a readable, unminified form.
&lt;/p&gt;
&lt;!-- Lisp, Emacs and mathematics --&gt;
&lt;p class=&quot;question&quot; id=&quot;lisp-emacs-and-mathematics&quot;&gt;
  How did you first discover computing, then end up with Lisp, Emacs
  and mathematics?
&lt;/p&gt;
&lt;p&gt;
  I got introduced to computers through the Logo programming language
  as a kid.  Using simple arithmetic, geometry, logic and code to
  manipulate a two-dimensional world had a lasting effect on me.
&lt;/p&gt;
&lt;p&gt;
  I still vividly remember how I ended up with Lisp.  It was at an
  airport during a long layover in 2007.  I wanted to use the time to
  learn something, so I booted my laptop
  running &lt;a href=&quot;https://www.debian.org/&quot;&gt;Debian&lt;/a&gt; GNU/Linux 4.0
  (Etch) and then started
  &lt;a href=&quot;https://www.gnu.org/software/clisp/&quot;&gt;GNU CLISP&lt;/a&gt; 2.41.
  In those days, Wi-Fi in airports was uncommon.  Smartphones and
  mobile data were also uncommon.  So it was fortunate that I had
  CLISP already installed on my system and my laptop was ready for
  learning Common Lisp.  I had it installed because I had wanted to
  learn Common Lisp for some time.  I was especially attracted by its
  simplicity, by the fact that the entire language can be built up
  from a very small set of special forms.  I
  use &lt;a href=&quot;https://www.sbcl.org/&quot;&gt;SBCL&lt;/a&gt; these days, by the way.
&lt;/p&gt;
&lt;p&gt;
  I discovered Emacs through Common Lisp.  Several sources recommended
  using the &lt;a href=&quot;https://slime.common-lisp.dev/&quot;&gt;Superior Lisp
  Interaction Mode for Emacs (SLIME)&lt;/a&gt; for Common Lisp programming,
  so that&apos;s where I began.  For many years I continued to use Vim as
  my primary editor, while relying on Emacs and SLIME for Lisp
  development.  Over time, as I learnt more about Emacs itself, I grew
  fond of Emacs Lisp and eventually made Emacs my primary editor and
  computing environment.
&lt;/p&gt;
&lt;p&gt;
  I have loved mathematics since my childhood days.  What has always
  fascinated me is how we can prove deep and complex facts using first
  principles and clear logical steps.  That feeling of certainty and
  rigour is unlike anything else.
&lt;/p&gt;
&lt;p&gt;
  Over the years, my love for the subject has been rekindled many
  times.  As a specific example, let me share how I got into number
  theory.  One day I decided to learn the RSA cryptosystem.  As I was
  working through the
  &lt;a href=&quot;https://people.csail.mit.edu/rivest/Rsapaper.pdf&quot;&gt;RSA
  paper&lt;/a&gt;, I stumbled upon the Euler totient function
  \( \varphi(n) \) which gives the number of positive integers not
  exceeding n that are relatively prime to n.  The paper first states
  that

  \[
    \varphi(p) = p - 1
  \]

  for prime numbers \( p.  \)  That was obvious since \( p \) has no
  factors other than \( 1 \) and itself, so every integer from \( 1 \)
  up to \( p - 1 \) must be relatively prime to it.  But then it
  presents

  \[
    \varphi(pq) = \varphi(p) \cdot \varphi(q) = (p - 1)(q - 1)
  \]

  for primes \( p \) and \( q.  \)  That was not immediately obvious to
  me back then.  After a few minutes of thinking, I managed to prove
  it from scratch.  By the inclusion-exclusion principle, we count how
  many integers from \( 1 \) up to \( pq \) are not divisible by
  \(p \) or \( q.  \)  There are \( pq \) integers in total.  Among
  them, there are \( q \) integers divisible by \( p \) and \( p \)
  integers divisible by \( q.  \)  So we need to subtract \( p + q \)
  from \(pq.  \)  But since one integer (\( pq \) itself) is counted in
  both groups, we add \( 1 \) back.  Therefore

  \[
    \varphi(pq) = pq - (p + q) + 1 = (p - 1)(q - 1).
  \]

  Next I could also obtain the general formula for \( \varphi(n) \)
  for an arbitrary positive integer \( n \) using the same idea.
  There are several other proofs too, but that is how I derived the
  general formula for \( \varphi(n) \) when I first encountered it.
  And just like that, I had begun to learn number theory!
&lt;/p&gt;
&lt;!-- Computing for fun --&gt;
&lt;p class=&quot;question&quot; id=&quot;computing-for-fun&quot;&gt;
  You&apos;ve said you prefer computing for fun.  What is fun to you?  Do
  you have an idea of what makes something fun or not?
&lt;/p&gt;
&lt;p&gt;
  For me, fun in computing began when I first learnt IBM/LCSI PC Logo
  when I was nine years old.  I had very limited access to computers
  back then, perhaps only about two hours per &lt;em&gt;month&lt;/em&gt; in the
  computer laboratory at my primary school.  Most of my Logo
  programming happened with pen and paper at home.  I would &apos;test&apos; my
  programs by tracing the results on graph paper.  Eventually I would
  get about thirty minutes of actual computer time in the lab to run
  them for real.
&lt;/p&gt;
&lt;p&gt;
  So back then, most of my computing happened without an actual
  computer.  But even with that limited access to computers, a whole
  new world opened up for me: one that showed me the joy of computing
  and more importantly, the joy of sharing my little programs with my
  friends and teachers.  One particular Logo program I still remember
  very well drew a house with animated dashed lines, where the dashes
  moved around the outline of the house.  Everyone around me loved it,
  copied it and tweaked it to change the colours, alter the details
  and add their own little touches.
&lt;/p&gt;
&lt;p&gt;
  For me, fun in computing comes from such exploration and sharing.  I
  enjoy asking &apos;what happens if&apos; and then seeing where it leads me.
  My Emacs package
  &lt;a href=&quot;https://elpa.nongnu.org/nongnu/devil.html&quot;&gt;devil-mode&lt;/a&gt;
  comes from such exploration.  It came from asking, &apos;What happens if
  we avoid using the &lt;kbd&gt;ctrl&lt;/kbd&gt; and &lt;kbd&gt;meta&lt;/kbd&gt; modifier keys
  and use &lt;kbd&gt;,&lt;/kbd&gt; (the comma key) or another suitable key as a
  leader key instead?  And can we still have a non-modal editing
  experience?&apos;
&lt;/p&gt;
&lt;p&gt;
  Sometimes computing for fun may mean crafting a minimal esoteric
  drawing language, making a small game or building a tool that solves
  an interesting problem elegantly.  It is a bonus if the exploration
  results in something working well enough that I can share with
  others on the World Wide Web and others find it fun too.
&lt;/p&gt;
&lt;!-- Pursuits --&gt;
&lt;p class=&quot;question&quot; id=&quot;interests-and-exploration&quot;&gt;
  How do you choose what to investigate?  Which most interest you,
  with what commonalities?
&lt;/p&gt;
&lt;p&gt;
  For me, it has always been one exploration leading to another.
&lt;/p&gt;
&lt;p&gt;
  For example, I originally built
  &lt;a href=&quot;https://github.com/susam/mathb&quot;&gt;MathB&lt;/a&gt; for my friends
  and myself who were going through a phase in our lives when we used
  to challenge each other with mathematical puzzles.  This tool became
  a nice way to share solutions with each other.  Its use spread from
  my friends to their friends and colleagues, then to schools and
  universities and eventually to IRC channels.
&lt;/p&gt;
&lt;p&gt;
  Similarly, I built &lt;a href=&quot;https://github.com/susam/texme&quot;&gt;TeXMe&lt;/a&gt;
  when I was learning neural networks and taking a lot of notes on the
  subject.  I was not ready to share the notes online, but I did want
  to share them with my friends and colleagues who were also learning
  the same topic.  Normally I would write my notes in LaTeX, compile
  them to PDF and share the PDF, but in this case, I wondered, what if
  I took some of the code from MathB and created a tool that would let
  me write plain Markdown
  (&lt;a href=&quot;https://github.github.com/gfm/&quot;&gt;GFM&lt;/a&gt;) + LaTeX
  (&lt;a href=&quot;https://www.mathjax.org/&quot;&gt;MathJax&lt;/a&gt;) in
  a &lt;code&gt;.html&lt;/code&gt; file and have the tool render the file as soon
  as it was opened in a web browser?  That resulted in TeXMe, which
  has surprisingly become one of my most popular projects, receiving
  &lt;a href=&quot;files/blog/texme-may-2025.png&quot;&gt;millions of hits&lt;/a&gt; in some
  months according to the CDN statistics.
&lt;/p&gt;
&lt;p&gt;
  Another example is &lt;a href=&quot;https://susam.github.io/muboard/&quot;&gt;Muboard&lt;/a&gt;,
  which is a bit like an interactive mathematics chalkboard.  I built
  this when I was hosting an
  &lt;a href=&quot;journey-to-prime-number-theorem.html&quot;&gt;analytic number
  theory book club&lt;/a&gt; and I needed a way to type LaTeX snippets live
  on screen and see them immediately rendered.  That made me wonder:
  what if I took TeXMe, made it interactive and gave it a chalkboard
  look-and-feel?  That led to Muboard.
&lt;/p&gt;
&lt;p&gt;
  So we can see that sharing mathematical notes and snippets has been
  a recurring theme in several of my projects.  But that is only a
  small fraction of my interests.  I have a wide variety of interests
  in computing.  I also engage in random explorations, like writing
  IRC clients
  (&lt;a href=&quot;https://github.com/susam/nimb&quot;&gt;NIMB&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/susam/tzero&quot;&gt;Tzero&lt;/a&gt;),
  ray tracing
  (&lt;a href=&quot;https://github.com/susam/pov25&quot;&gt;POV-Ray&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/spxy/java-ray-tracing&quot;&gt;Java ray tracer&lt;/a&gt;),
  writing Emacs guides
  (&lt;a href=&quot;https://github.com/susam/emacs4cl&quot;&gt;Emacs4CL&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/susam/emfy&quot;&gt;Emfy&lt;/a&gt;),
  developing small single-file HTML games
  (&lt;a href=&quot;invaders.html&quot;&gt;Andromeda Invaders&lt;/a&gt;,
  &lt;a href=&quot;myrgb.html&quot;&gt;Guess My RGB&lt;/a&gt;),
  purely recreational programming
  (&lt;a href=&quot;fxyt.html&quot;&gt;FXYT&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/susam/may4&quot;&gt;may4.fs&lt;/a&gt;,
  &lt;a href=&quot;self-printing-machine-code.html&quot;&gt;self-printing machine code&lt;/a&gt;,
  &lt;a href=&quot;primegrid.html&quot;&gt;prime number grid explorer&lt;/a&gt;)
  and so on.  The list goes on.  When it comes to hobby computing, I
  don&apos;t think I can pick just one domain and say it interests me the
  most.  I have a lot of interests.
&lt;/p&gt;
&lt;!-- What is computing?  --&gt;
&lt;p class=&quot;question&quot; id=&quot;computing-activities&quot;&gt;
  What is computing, to you?
&lt;/p&gt;
&lt;p&gt;
  Computing, to me, covers a wide range of activities: programming a
  computer, using a computer, understanding how it works, even
  building one.  For example, I once built a tiny 16-bit CPU along
  with a small main memory that could hold only eight 16-bit
  instructions, using VHDL and a Xilinx CPLD kit.  The design was
  based on the Mano CPU introduced in the book &lt;em&gt;Computer System
  Architecture&lt;/em&gt; (3rd ed.) by M. Morris Mano.  It was incredibly
  fun to enter instructions into the main memory, one at a time, by
  pushing DIP switches up and down and then watch the CPU I had built
  execute an entire program.  For someone like me, who usually works
  with software at higher levels of abstraction, that was a thrilling
  experience!
&lt;/p&gt;
&lt;p&gt;
  Beyond such experiments, computing also includes more practical and
  concrete activities, such as installing and using my favourite Linux
  distribution (Debian), writing software tools in languages like
  Common Lisp, Emacs Lisp, Python and the shell command language or
  customising my Emacs environment to automate repetitive tasks.
&lt;/p&gt;
&lt;p&gt;
  To me, computing also includes the abstract stuff like spending time
  with abstract algebra and number theory and getting a deeper
  understanding of the results pertaining to groups, rings and fields,
  as well as numerous number-theoretic results.  Browsing the
  &lt;a href=&quot;https://oeis.org/&quot;&gt;On-Line Encyclopedia of Integer
  Sequences&lt;/a&gt; (OEIS), writing small programs to explore interesting
  sequences or just thinking about them is computing too.  I think
  many of the interesting results in computer science have deep
  mathematical foundations.  I believe much of computer science is
  really discrete mathematics in action.
&lt;/p&gt;
&lt;p&gt;
  And if we dive all the way down from the CPU to the level of
  transistors, we encounter continuous mathematics as well, with
  non-linear voltage-current relationships and analogue behaviour that
  make digital computing possible.  It is fascinating how, as a
  relatively new species on this planet, we have managed to take sand
  and find a way to use continuous voltages and currents in electronic
  circuits built with silicon and convert them into the discrete
  operations of digital logic.  We have machines that can simulate
  themselves!
&lt;/p&gt;
&lt;p&gt;
  To me, all of this is fun.  To study and learn about these things,
  to think about them, to understand them better and to accomplish
  useful or amusing results with this knowledge is all part of the
  fun.
&lt;/p&gt;
&lt;!-- Programming vs domains --&gt;
&lt;p class=&quot;question&quot; id=&quot;programming-vs-domains&quot;&gt;
  How do you view programming vs. domains?
&lt;/p&gt;
&lt;p&gt;
  I focus more on the domain than the tool.  Most of the time it is a
  problem that catches my attention and then I explore it to
  understand the domain and arrive at a solution.  The problem itself
  usually points me to one of the tools I already know.
&lt;/p&gt;
&lt;p&gt;
  For example, if it is about working with text files, I might write
  an Emacs Lisp function.  If it involves checking large sets of
  numbers rapidly for patterns, I might choose C++ or Rust.  But if I
  want to share interactive visualisations of those patterns with
  others, I might rewrite the solution in HTML and JavaScript,
  possibly with the use of the Canvas API, so that I can share the
  work as a self-contained file that others can execute easily within
  their web browsers.  When I do that, I prefer to keep the HTML neat
  and readable, rather than bundled or minified, so that people who
  like to &apos;View Source&apos; can copy, edit and customise the code
  themselves to immediately see their changes take effect.
&lt;/p&gt;
&lt;p&gt;
  Let me share a specific example.  While working on a web-based game, I first
  used &lt;code&gt;CanvasRenderingContext2D&lt;/code&gt;&apos;s &lt;code&gt;fillText()&lt;/code&gt;
  to display text on the game canvas.  However, dissatisfied with the
  text rendering quality, I began looking for IBM PC OEM fonts and
  similar retro fonts online.  After downloading a few font packs, I
  wrote a little Python script to convert them to bitmaps (arrays of
  integers) and then used the bitmaps to draw text on the canvas using
  JavaScript, one cell at a time, to get pixel-perfect results!  These
  tiny Python and JavaScript tools were good enough that I felt
  comfortable sharing them together as a tiny toolkit called
  &lt;a href=&quot;https://susam.github.io/pcface/src/demo.html&quot;&gt;PCFace&lt;/a&gt;.
  This toolkit offers JavaScript bitmap arrays and tiny JavaScript
  rendering functions, so that someone else who wants to display text
  on their game canvas using PC fonts and nothing but plain HTML and
  JavaScript can do so without having to solve the problem from
  scratch!
&lt;/p&gt;
&lt;!-- Applicability of old functionality for new problems --&gt;
&lt;p class=&quot;question&quot; id=&quot;old-functionality-and-new-problems&quot;&gt;
  Has the rate of your making new Emacs functions has diminished over
  time (as if everything&apos;s covered) or do the widening domains lead to
  more?  I&apos;m curious how applicable old functionality is for new
  problems and how that impacts the APIs!
&lt;/p&gt;
&lt;p&gt;
  My rate of making new Emacs functions has definitely decreased.
  There are two reasons.  One is that over the years my computing
  environment has converged into a comfortable, stable setup I am very
  happy with.  The other is that at this stage of life I simply cannot
  afford the time to endlessly tinker with Emacs as I did in my
  younger days.
&lt;/p&gt;
&lt;p&gt;
  More generally, when it comes to APIs, I find that well-designed
  functionality tends to remain useful even when new problems appear.
  In Emacs, for example, many of my older functions continue to serve
  me well because they were written in a composable way.  New problems
  can often be solved with small wrappers or combinations of existing
  functions.  I think APIs that consist of functions that are simple,
  orthogonal and flexible age well.  If each function in an API does
  one thing and does it well (the Unix philosophy), it will have
  long-lasting utility.
&lt;/p&gt;
&lt;p&gt;
  Of course, new domains and problems do require new functions and
  extensions to an API, but I think it is very important to not give
  in to the temptation of enhancing the existing functions by making
  them more complicated with optional parameters, keyword arguments,
  nested branches and so on.  Personally, I have found that it is much
  better to implement new functions that are small, orthogonal and
  flexible, each doing one thing and doing it well.
&lt;/p&gt;
&lt;p class=&quot;question&quot; id=&quot;designing-for-composability&quot;&gt;
  What design methods or tips do you have, to increase composability?
&lt;/p&gt;
&lt;p&gt;
  For me, good design starts with good vocabulary.  Clear vocabulary
  makes abstract notions concrete and gives collaborators a shared
  language to work with.  For example, while working on a network
  events database many years ago, we collected data minute by minute
  from network devices.  We decided to call each minute of data from a
  single device a &apos;nugget&apos;.  So if we had 15 minutes of data from 10
  devices, that meant 150 nuggets.
&lt;/p&gt;
&lt;p&gt;
  Why &apos;nugget&apos;?  Because it was shorter and more convenient than
  repeatedly saying &apos;a minute of data from one device&apos;.  Why not
  something less fancy like &apos;chunk&apos;?  Because we reserved &apos;chunk&apos; for
  subdivisions within a nugget.  Perhaps there were better choices,
  but &apos;nugget&apos; was the term we settled on and it quickly became shared
  terminology between the collaborators.  Good terminology naturally
  carries over into code.  With this vocabulary in place, function
  names like &lt;code&gt;collect_nugget()&lt;/code&gt;,
  &lt;code&gt;open_nugget()&lt;/code&gt;, &lt;code&gt;parse_chunk()&lt;/code&gt;,
  &lt;code&gt;index_chunk()&lt;/code&gt;, &lt;code&gt;skip_chunk()&lt;/code&gt;,
  etc. immediately become meaningful to everyone involved.
&lt;/p&gt;
&lt;p&gt;
  Thinking about the vocabulary also ensures that we are thinking
  about the data, concepts and notions we are working with in a
  deliberate manner and that kind of thinking also helps when we
  design the architecture of software.
&lt;/p&gt;
&lt;p&gt;
  Too often I see collaborators on software projects jump straight
  into writing functions that take some input and produce some desired
  effect, with variable names and function names decided on the fly.
  To me, this feels backwards.  I prefer the opposite approach.
  Define the terms first and let the code follow from them.
&lt;/p&gt;
&lt;p&gt;
  I also prefer developing software in a layered manner, where complex
  functionality is built from simpler, well-named building blocks.  It
  is especially important to avoid &lt;em&gt;layer violations&lt;/em&gt;, where
  one complex function invokes another complex function.  That creates
  tight coupling between two complex functions.  If one function
  changes in the future, we have to reason carefully about how it
  affects the other.  Since both are already complex, the cognitive
  burden is high.  A better approach, I think, is to identify the
  common functionality they share and factor that out into smaller,
  simpler functions.
&lt;/p&gt;
&lt;p&gt;
  To summarise, I like to develop software with a clear vocabulary,
  consistent use of that vocabulary, a layered design where complex
  functions are built from simpler ones and by avoiding layer
  violations.  I am sure none of this is new to the Lobsters
  community.  Some of these ideas also occur
  in &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain-driven_design&quot;&gt;domain-driven
  design&lt;/a&gt; (DDD).  DDD defines the term &lt;em&gt;ubiquitous language&lt;/em&gt;
  to mean, &apos;A language structured around the domain model and used by
  all team members within a bounded context to connect all the
  activities of the team with the software.&apos;  If I could call this
  approach of software development something, I would simply call it
  &apos;vocabulary-driven development&apos; (VDD), though of course DDD is the
  more comprehensive concept.
&lt;/p&gt;
&lt;p&gt;
  Like I said, none of this is likely new to the Lobsters community.
  In particular, I suspect Forth programmers would find it too
  obvious.  In Forth, it is very difficult to begin with a long,
  poorly thought-out monolithic word and then break it down into
  smaller ones later.  The stack effects quickly become too hard to
  track mentally with that approach.  The only viable way to develop
  software in Forth is to start with a small set of words that
  represent the important notions of the problem domain, test them
  immediately and then compose higher-level words from the lower-level
  ones.  Forth naturally encourages a layered style of development,
  where the programmer thinks carefully about the domain, invents
  vocabulary and expresses complex ideas in terms of simpler ones,
  almost in a mathematical fashion.  In my experience, this kind of
  deliberate design produces software that remains easy to understand
  and reason about even years after it was written.
&lt;/p&gt;
&lt;!-- Small vs large functions --&gt;
&lt;p class=&quot;question&quot; id=&quot;small-vs-large-functions&quot;&gt;
  Not enhancing existing functions but adding new small ones seems
  quite lovely, but how do you come back to such a codebase later with
  many tiny functions?  At points, I&apos;ve advocated for very large
  functions, particularly traumatized by Java-esque 1000 functions in
  1000 files approaches.  When you had time, would you often
  rearchitecture the conceptual space of all of those functions?
&lt;/p&gt;
&lt;p&gt;
  The famous quote from Alan J. Perlis comes to mind:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    It is better to have 100 functions operate on one data structure
    than 10 functions on 10 data structures.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  Personally, I enjoy working with a codebase that has thousands of
  functions, provided most of them are small, well-scoped and do one
  thing well.  That said, I am not dogmatically opposed to large
  functions.  It is always a matter of taste and judgement.  Sometimes
  one large, cohesive function is clearer than a pile of tiny ones.
&lt;/p&gt;
&lt;p&gt;
  For example, when I worked on parser generators, I often found that
  lexers and finite state machines benefited from a single top-level
  function containing the full tokenisation logic or the full state
  transition logic in one place.  That function could call smaller
  helpers for specific tasks, but we still need the overall
  &lt;code&gt;switch&lt;/code&gt;-&lt;code&gt;case&lt;/code&gt; or
  &lt;code&gt;if&lt;/code&gt;-&lt;code&gt;else&lt;/code&gt; or &lt;code&gt;cond&lt;/code&gt; ladder
  somewhere.  I think trying to split that ladder into smaller
  functions would only make the code harder to follow.
&lt;/p&gt;
&lt;p&gt;
  So while I lean towards small, composable functions, the real goal
  is to strike a balance that keeps code maintainable in the long run.
  Each function should be as small as it can reasonably be and no
  smaller.
&lt;/p&gt;
&lt;!-- Domains --&gt;
&lt;p class=&quot;question&quot; id=&quot;domains-and-projects&quot;&gt;
  Like you, I program as a tool to explore domains.  Which do you know
  the most about?
&lt;/p&gt;
&lt;p&gt;
  For me too, the appeal of computer programming lies especially in
  how it lets me explore different domains.  There are two kinds of
  domains in which I think I have gained good expertise.  The first
  comes from years of developing software for businesses, which has
  included solving problems such as network events parsing, indexing
  and querying, packet decoding, developing parser generators,
  database session management and TLS certificate lifecycle
  management.  The second comes from areas I pursue purely out of
  curiosity or for hobby computing.  This is the kind I am going to
  focus on in our conversation.
&lt;/p&gt;
&lt;p&gt;
  Although computing and software are serious business today, for me,
  as for many others, computing is also a hobby.
&lt;/p&gt;
&lt;p&gt;
  Personal hobby projects often lead me down various rabbit holes and
  I end up learning new domains along the way.  For example, although
  I am not a web developer, I learnt to build small, interactive
  single-page tools in plain HTML, CSS and JavaScript simply because I
  needed them for my hobby projects over and over again.  An early
  example is &lt;a href=&quot;quickqwerty.html&quot;&gt;QuickQWERTY&lt;/a&gt;, which I built
  to teach myself and my friends touch-typing on QWERTY keyboards.
  Another example is &lt;a href=&quot;cfrs.html&quot;&gt;CFRS[]&lt;/a&gt;, which I created
  because I wanted to make a total (non-Turing complete) drawing
  language that has turtle graphics like Logo but is absolutely
  minimal like P&amp;prime;&amp;prime;.
&lt;/p&gt;
&lt;!-- Double spacing --&gt;
&lt;p class=&quot;question&quot; id=&quot;double-spacing-and-touch-typing&quot;&gt;
  You use double spaces after periods which I&apos;d only experienced from
  people who learned touch typing on typewriters, unexpected!
&lt;/p&gt;
&lt;p&gt;
  Yes, I do separate sentences by double spaces.  It is interesting
  that you noticed this.
&lt;/p&gt;
&lt;p&gt;
  I once briefly learnt touch typing on typewriters as a kid, but
  those lessons did not stick with me.  It was much later, when I used
  a Java applet-based touch typing tutor that I found online about two
  decades ago, that the lessons really stayed with me.  Surprisingly,
  that application taught me to type with a single space between
  sentences.  By the way, I disliked installing Java plugins into the
  web browser, so I wrote &lt;a href=&quot;quickqwerty.html&quot;&gt;QuickQWERTY&lt;/a&gt;
  as a similar touch typing tutor in plain HTML and JavaScript for
  myself and my friends.
&lt;/p&gt;
&lt;p&gt;
  I learnt to use double spaces between sentences first with Vim and
  then later again with Emacs.  For example, in Vim,
  the &lt;code&gt;joinspaces&lt;/code&gt; option is on by default, so when we join
  sentences with the normal mode command &lt;code&gt;J&lt;/code&gt; or format
  paragraphs with &lt;code&gt;gqap&lt;/code&gt;, Vim inserts two spaces after full
  stops.  We need to disable that behaviour with &lt;code&gt;:set
  nojoinspaces&lt;/code&gt; if we want single spacing.
&lt;/p&gt;
&lt;p&gt;
  It is similar in Emacs.  In Emacs, the
  &lt;code&gt;delete-indentation&lt;/code&gt; command (&lt;code&gt;M-^&lt;/code&gt;) and
  the &lt;code&gt;fill-paragraph&lt;/code&gt; command (&lt;code&gt;M-q&lt;/code&gt;) both
  insert two spaces between sentences by default.  Single spacing can
  be enabled with &lt;code&gt;(setq sentence-end-double-space nil)&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
  Incidentally, I spend a good portion of the README for my Emacs
  quick-start DIY kit named
  &lt;a href=&quot;https://github.com/susam/emfy&quot;&gt;Emfy&lt;/a&gt; discussing sentence
  spacing conventions under the section
  &lt;a href=&quot;https://github.com/susam/emfy#single-space-for-sentence-spacing&quot;&gt;Single
  Space for Sentence Spacing&lt;/a&gt;.  There I explain how to configure
  Emacs to use single spaces, although I use double spaces myself.
  That&apos;s because many new Emacs users prefer single spacing.
&lt;/p&gt;
&lt;p&gt;
  The defaults in Vim and Emacs made me adopt double spacing.  The
  double spacing convention is also widespread across open source
  software.  If we look at the Vim help pages, Emacs built-in
  documentation or the Unix and Linux man pages, double spacing is the
  norm.  Even inline comments in traditional open source projects
  often use it.  For example, see Vim&apos;s
  &lt;a href=&quot;https://github.com/vim/vim/blob/v9.1.1752/runtime/doc/usr_01.txt&quot;&gt;:h usr_01.txt&lt;/a&gt;,
  Emacs&apos;s
  &lt;a href=&quot;https://cgit.git.savannah.gnu.org/cgit/emacs.git/tree/doc/emacs/emacs.texi?h=emacs-30.2#n1556&quot;&gt;(info &quot;(emacs) Intro&quot;)&lt;/a&gt;
  or the comments in the &lt;a href=&quot;https://gcc.gnu.org/git/?p=gcc.git;f=gcc/cfg.cc;hb=releases/gcc-15.2.0&quot;&gt;GCC source code&lt;/a&gt;.
&lt;/p&gt;
&lt;!-- Learning --&gt;
&lt;p class=&quot;question&quot; id=&quot;approach-to-learning&quot;&gt;
  How do you approach learning a new domain?
&lt;/p&gt;
&lt;p&gt;
  When I take on a new domain, there is of course a lot of reading
  involved from articles, books and documentation.  But as I read, I
  constantly try to test what I learn.  Whenever I see a claim, I ask
  myself, &apos;If this claim were wrong, how could I demonstrate it?&apos;
  Then I design a little experiment, perhaps write a snippet of code
  or run a command or work through a concrete example, with the goal
  of checking the claim in practice.
&lt;/p&gt;
&lt;p&gt;
  Now I am not genuinely hoping to prove a claim wrong.  It is just a
  way to engage with the material.  To illustrate, let me share an
  extremely simple and generic example without going into any
  particular domain.  Suppose I learn that Boolean operations in
  Python short-circuit.  I might write out several experimental
  snippets like the following:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def t(): print(&apos;t&apos;); return True
def f(): print(&apos;f&apos;); return False
f() or t() or f()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  And then confirm that the results do indeed confirm short-circuit
  evaluation (&lt;code&gt;f&lt;/code&gt; followed by &lt;code&gt;t&lt;/code&gt; in this case).
&lt;/p&gt;
&lt;p&gt;
  At this point, one could say, &apos;Well, you just confirmed what the
  documentation already told you.&apos;  And that&apos;s true.  But for me, the
  value lies in trying to test it for myself.  Even if the claim
  holds, the act of checking forces me to see the idea in action.
  That not only reinforces the concept but also helps me build a much
  deeper intuition for it.
&lt;/p&gt;
&lt;p&gt;
  Sometimes these experiments also expose gaps in my own
  understanding.  Suppose I didn&apos;t properly know what &apos;short-circuit&apos;
  means.  Then the results might contradict my expectations.  That
  contradiction would push me to correct my misconception and that&apos;s
  where the real learning happens.
&lt;/p&gt;
&lt;p&gt;
  Occasionally, this process even uncovers subtleties I didn&apos;t expect.
  For example, while learning socket programming, I discovered that a
  client can successfully receive data using &lt;code&gt;recv()&lt;/code&gt; even
  after calling &lt;code&gt;shutdown()&lt;/code&gt;, contrary to what I had first
  inferred from the specifications.  See my Stack Overflow post
  &lt;a href=&quot;https://stackoverflow.com/q/39698037/303363&quot;&gt;Why can recv()
  receive messages after the client has invoked shutdown()?&lt;/a&gt; for
  more details if you are curious.
&lt;/p&gt;
&lt;p&gt;
  Now this method cannot always be applied, especially if it is very
  expensive or unwieldy to do so.  For example, if I am learning
  something in the finance domain, it is not always possible to
  perform an actual transaction.  One can sometimes use simulation
  software, mock environments or sandbox systems to explore ideas
  safely.  Still, it is worth noting that this method has its
  limitations.
&lt;/p&gt;
&lt;p&gt;
  In mathematics, though, I find this method highly effective.  When I
  study a new branch of mathematics, I try to come up with examples
  and counterexamples to test what I am learning.  Often, failing to
  find a counterexample helps me appreciate more deeply why a claim
  holds and why no counterexamples exist.
&lt;/p&gt;
&lt;!-- Distraction --&gt;
&lt;p class=&quot;question&quot; id=&quot;managing-time-and-distractions&quot;&gt;
  Do you have trouble not getting distracted with so much on your
  plate?  I&apos;m curious how you balance the time commitments of
  everything!
&lt;/p&gt;
&lt;p&gt;
  Indeed, it is very easy to get distracted.  One thing that has
  helped over the years is the increase in responsibilities in other
  areas of my life.  These days I also spend some of my free time
  studying mathematics textbooks.  With growing responsibilities and
  the time I devote to mathematics, I now get at most a few hours each
  week for hobby computing.  This automatically narrows down my
  options.  I can explore perhaps one or at most two ideas in a month
  and that constraint makes me very deliberate about choosing my
  pursuits.
&lt;/p&gt;
&lt;p&gt;
  Many of the explorations do not evolve into something solid that I
  can share.  They remain as little experimental code snippets or
  notes archived in a private repository.  But once in a while, an
  exploration grows into something concrete and feels worth sharing on
  the Web.  That becomes a short-term hobby project.  I might work on
  it over a weekend if it is small or for a few weeks if it is more
  complex.  When that happens, the goal of sharing the project helps
  me focus.
&lt;/p&gt;
&lt;p&gt;
  I try not to worry too much about making time.  After all, this is
  just a hobby.  Other areas of my life have higher priority.  I also
  want to devote a good portion of my free time to learning more
  mathematics, which is another hobby I am passionate about.  Whatever
  little spare time remains after attending to the higher-priority
  aspects of my life goes into my computing projects, usually a couple
  of hours a week, most of it on weekends.
&lt;/p&gt;
&lt;!-- Blogging --&gt;
&lt;p class=&quot;question&quot; id=&quot;blogging&quot;&gt;
  How does blogging mix in?  What&apos;s the development like of a single
  piece of curiosity through wrestling with the domain, learning and
  sharing it etc.?
&lt;/p&gt;
&lt;p&gt;
  Maintaining my personal website is another aspect of computing that
  I find very enjoyable.  My website began as a loose collection of
  pages on a LAN site during my university days.  Since then I have
  been adding pages to it to write about various topics that I find
  interesting.  It acquired its blog shape and form much later when
  blogging became fashionable.
&lt;/p&gt;
&lt;p&gt;
  I usually write a new blog post when I feel like there is some piece
  of knowledge or some exploration that I want to archive in a
  persistent format.  Now what the development of a post looks like
  depends very much on the post.  So let me share two opposite
  examples to describe what the development of a single piece looks
  like.
&lt;/p&gt;
&lt;p&gt;
  One of my most frequently visited posts
  is &lt;a href=&quot;lisp-in-vim.html&quot;&gt;Lisp in Vim&lt;/a&gt;.  It started when I
  was hosting a Common Lisp programming club for beginners.  Although
  I have always used Emacs and SLIME for Common Lisp programming
  myself, many in the club used Vim, so I decided to write a short
  guide on setting up something SLIME-like there.  As a former
  long-time Vim user myself, I wanted to make the Lisp journey easier
  for Vim users too.  I thought it would be a 30-minute exercise where
  I write up a README that explains how to install
  &lt;a href=&quot;https://github.com/kovisoft/slimv&quot;&gt;Slimv&lt;/a&gt; and how to set
  it up in Vim.  But then I discovered a newer plugin called
  &lt;a href=&quot;https://github.com/vlime/vlime&quot;&gt;Vlime&lt;/a&gt; that also offered
  SLIME-like features in Vim!  That detail sent me down a very deep
  rabbit hole.  Now I needed to know how the two packages were
  different, what their strengths and weaknesses were, how routine
  operations were performed in both and so on.  What was meant to be a
  short note turned into a nearly 10,000-word article.  As I was
  comparing the two SLIME-like packages for Vim, I also found a few
  bugs in Slimv and contributed fixes for them
  (&lt;a href=&quot;https://github.com/kovisoft/slimv/pull/87&quot;&gt;#87&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/kovisoft/slimv/pull/88&quot;&gt;#88&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/kovisoft/slimv/pull/89&quot;&gt;#89&lt;/a&gt;,
  &lt;a href=&quot;https://github.com/kovisoft/slimv/pull/90&quot;&gt;#90&lt;/a&gt;).
  Writing this blog post turned into a month-long project!
&lt;/p&gt;
&lt;p&gt;
  At the opposite extreme is a post like
  &lt;a href=&quot;elliptical-python-programming.html&quot;&gt;Elliptical
  Python Programming&lt;/a&gt;.  I stumbled upon Python&apos;s
  &lt;a href=&quot;https://docs.python.org/3/library/constants.html#Ellipsis&quot;&gt;Ellipsis&lt;/a&gt;
  while reviewing someone&apos;s code.  It immediately caught my attention.
  I wondered if, combined with some standard obfuscation techniques,
  one could write arbitrary Python programs that looked almost like
  Morse code.  A few minutes of experimentation showed that a
  genuinely Morse code-like appearance was not possible, but something
  close could be achieved.  So I wrote what I hope is a humorous post
  demonstrating that arbitrary Python programs can be written using a
  very restricted set of symbols, one of which is the ellipsis.  It
  took me less than an hour to write this post.  The final result
  doesn&apos;t look quite like Morse code as I had imagined, but it is
  quite amusing nevertheless!
&lt;/p&gt;
&lt;!-- Forums --&gt;
&lt;p class=&quot;question&quot; id=&quot;forums&quot;&gt;
  What draws you to post and read online forums?  How do you balance
  or allot time for reading technical articles, blogs etc.?
&lt;/p&gt;
&lt;p&gt;
  The exchange of ideas!  Just as I enjoy sharing my own
  computing-related thoughts, ideas and projects, I also find joy in
  reading what others have to share.
&lt;/p&gt;
&lt;p&gt;
  Other areas of my life take precedence over hobby projects and hobby
  projects take precedence over technical forums.
&lt;/p&gt;
&lt;p&gt;
  After I&apos;ve given time to the higher-priority parts of my life and to
  my own technical explorations, I use whatever spare time remains to
  read articles, follow technical discussions and occasionally add
  comments.
&lt;/p&gt;
&lt;!-- MathB.in --&gt;
&lt;p class=&quot;question&quot; id=&quot;mathb-moderation-problems&quot;&gt;
  When you decided to stop with MathB due to moderation burdens, I
  offered to take over/help and you mentioned others had too.  Did
  anyone end up forking it, to your knowledge?
&lt;/p&gt;
&lt;p&gt;
  I first thought of shutting down the
  &lt;a href=&quot;https://github.com/susam/mathb&quot;&gt;MathB&lt;/a&gt;-based pastebin
  website in November 2019.  The website had been running for seven
  years at that time.  When I announced my thoughts to the IRC
  communities that would be affected, I received a lot of support and
  encouragement.  A few members even volunteered to help me out with
  moderation.  That support and encouragement kept me going for
  another six years.  However, the volunteers eventually became busy
  with their own lives and moved on.  After all, moderating user
  content for an open pastebin that anyone in the world can post to is
  a thankless and tiring activity.  So most of the moderation activity
  fell back on me.  Finally, in February 2025, I realised that I no
  longer want to spend time on this kind of work.
&lt;/p&gt;
&lt;p&gt;
  I developed MathB with a lot of passion for myself and my friends.
  I had no idea at the time that this little project would keep a
  corner of my mind occupied even during weekends and holidays.  There
  was always a nagging worry.  What if someone posted content that
  triggered compliance concerns and my server was taken offline while
  I was away?  I no longer wanted that kind of burden in my life.  So
  I finally decided to shut it down.  I&apos;ve written more about this
  in &lt;a href=&quot;mathbin-is-shutting-down.html&quot;&gt;MathB.in Is Shutting
  Down&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  To my knowledge, no one has forked it, but others have developed
  alternatives.  Further, the
  &lt;a href=&quot;https://wiki.archiveteam.org/&quot;&gt;Archive Team&lt;/a&gt; has
  &lt;a href=&quot;https://web.archive.org/web/*/https://mathb.in/&quot;&gt;archived&lt;/a&gt;
  all posts from the now-defunct MathB-based website.  A member of the
  Archive Team reached out to me over IRC and we worked together for
  about a week to get everything successfully archived.
&lt;/p&gt;
&lt;!-- Textbooks --&gt;
&lt;p class=&quot;question&quot; id=&quot;favourite-mathematics-textbooks&quot;&gt;
  What&apos;re your favorite math textbooks?
&lt;/p&gt;
&lt;p&gt;
  I have several favourite mathematics books, but let me share three I
  remember especially fondly.
&lt;/p&gt;
&lt;p&gt;
  The first is &lt;em&gt;Advanced Engineering Mathematics&lt;/em&gt; by Erwin
  Kreyszig.  I don&apos;t often see this book recommended online, but for
  me it played a major role in broadening my horizons.  I think I
  studied the 8th edition back in the early 2000s.  It is a hefty book
  with over a thousand pages and I remember reading it cover to cover,
  solving every exercise problem along the way.  It gave me a solid
  foundation in routine areas like differential equations, linear
  algebra, vector calculus and complex analysis.  It also introduced
  me to Fourier transforms and Laplace transforms, which I found
  fascinating.
&lt;/p&gt;
&lt;p&gt;
  Of course, the Fourier transform has a wide range of applications in
  signal processing, communications, spectroscopy and more.  But I
  want to focus on the fun and playful part.  In the early 2000s, I
  was also learning to play the piano as a hobby.  I used to record my
  amateur music compositions with
  &lt;a href=&quot;https://github.com/audacity/audacity&quot;&gt;Audacity&lt;/a&gt; by
  connecting my digital piano to my laptop with a line-in cable.  It
  was great fun to plot the spectrum of my music on Audacity, apply
  high-pass and low-pass filters and observe how the Fourier transform
  of the audio changed and then hear the effect on the music.  That
  kind of hands-on tinkering made Fourier analysis intuitive for me
  and I highly recommend it to anyone who enjoys both music and
  mathematics.
&lt;/p&gt;
&lt;p&gt;
  The second book is &lt;em&gt;Introduction to Analytic Number Theory&lt;/em&gt;
  by Tom M.  Apostol.  As a child I was intrigued by the prime number
  theorem but lacked the mathematical maturity to understand its
  proof.  Years later, as an adult, I finally taught myself the proof
  from Apostol&apos;s book.  It was a fantastic journey that began with
  simple concepts like the Möbius function and Dirichlet products and
  ended with quite clever contour integrals that proved the theorem.
  The complex analysis I had learnt from Kreyszig turned out to be
  crucial for understanding those integrals.  Along the way I gained a
  deeper understanding of the Riemann zeta function \( \zeta(s).  \)
  The book discusses zero-free regions where \( \zeta(s) \) does not
  vanish, which I found especially fascinating.  Results like \(
  \zeta(-1) = -1/12, \) which once seemed mysterious, became obvious
  after studying this book.
&lt;/p&gt;
&lt;p&gt;
  The third is &lt;em&gt;Galois Theory&lt;/em&gt; by Ian Stewart.  It introduced
  me to field extensions, field homomorphisms and solubility by
  radicals.  I had long known that not all quintic equations are
  soluble by radicals, but I didn&apos;t know why.  Stewart&apos;s book taught
  me exactly why.  In particular, it demonstrated that the polynomial
  \( t^5 - 6t + 3 \) over the field of rational numbers is not soluble
  by radicals.  This particular result, although fascinating, is just
  a small part of a much larger body of work, which is even more
  remarkable.  To arrive at this result, the book takes us through a
  wonderful journey that includes the theory of polynomial rings,
  algebraic and transcendental field extensions, impossibility proofs
  for ruler-and-compass constructions, the Galois correspondence and
  much more.
&lt;/p&gt;
&lt;p&gt;
  One of the most rewarding aspects of reading books like these is how
  they open doors to new knowledge, including things I didn&apos;t even
  know that I didn&apos;t know.
&lt;/p&gt;
&lt;!-- Mathematics and computing --&gt;
&lt;p class=&quot;question&quot; id=&quot;mathematics-and-computing&quot;&gt;
  How does the newer math jell with or inform past or present
  computing, compared to much older stuff?
&lt;/p&gt;
&lt;p&gt;
  I don&apos;t always think explicitly about how mathematics informs
  computing, past or present.  Often the textbooks I pick feel very
  challenging to me, so much so that all my energy goes into simply
  mastering the material.  It is arduous but enjoyable.  I do it
  purely for the fun of learning without worrying about applications.
&lt;/p&gt;
&lt;p&gt;
  Of course, a good portion of pure mathematics probably has no
  real-world applications.  As G. H. Hardy famously wrote in &lt;em&gt;A
  Mathematician&apos;s Apology&lt;/em&gt;:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    I have never done anything &apos;useful&apos;.  No discovery of mine has
    made or is likely to make, directly or indirectly, for good or
    ill, the least difference to the amenity of the world.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  But there is no denying that some of it does find applications.
  Were Hardy alive today, he might be disappointed that number theory,
  his favourite field of &apos;useless&apos; mathematics, is now a crucial part
  of modern cryptography.  Electronic commerce wouldn&apos;t likely exist
  without it.
&lt;/p&gt;
&lt;p&gt;
  Similarly, it is amusing how something as abstract as abstract
  algebra finds very concrete applications in coding theory.  Concepts
  such as polynomial rings, finite fields and cosets of subspaces in
  vector spaces over finite fields play a crucial role in
  error-correcting codes, without which modern data transmission and
  storage would not be possible.
&lt;/p&gt;
&lt;p&gt;
  On a more personal note, some simpler areas of mathematics have been
  directly useful in my own work.  While solving problems for
  businesses, information entropy, combinatorics and probability
  theory were crucial when I worked on gesture-based authentication
  about one and a half decades ago.
&lt;/p&gt;
&lt;p&gt;
  Similarly, when I was developing Bloom filter-based indexing and
  querying for a network events database, again, probability theory
  was crucial in determining the parameters of the Bloom filters (such
  as the number of hash functions, bits per filter and elements per
  filter) to ensure that the false positive rate remained below a
  certain threshold.  Subsequent testing with randomly sampled network
  events confirmed that the observed false positive rate matched the
  theoretical estimate quite well.  It was very satisfying to see
  probability theory and the real world agreeing so closely.
&lt;/p&gt;
&lt;p&gt;
  Beyond these specific examples, studying mathematics also influences
  the way I think about problems.  Embarking on journeys like analytic
  number theory or Galois theory is humbling.  There are times when I
  struggle to understand a small paragraph of the book and it takes me
  several hours (or even days) to work out the arguments in detail
  with pen and paper (lots of it) before I really grok them.  That
  experience of grappling with dense reasoning teaches humility and
  also makes me sceptical of complex, hand-wavy logic in day-to-day
  programming.
&lt;/p&gt;
&lt;p&gt;
  Several times I have seen code that bundles too many decisions into
  one block of logic, where it is not obvious whether it would behave
  correctly in all circumstances.  Explanations may sometimes be
  offered about why it works for reasonable inputs, but the reasoning
  is often not watertight.  The experience of working through
  mathematical proofs, writing my own, making mistakes and then
  correcting them has taught me that if the reasoning for correctness
  is not clear and rigorous, something could be wrong.  In my
  experience, once such code sees real-world usage, a bug is nearly
  always found.
&lt;/p&gt;
&lt;p&gt;
  That&apos;s why I usually insist either on simplifying the logic or on
  demonstrating correctness in a clear, rigorous way.  Sometimes this
  means doing a case-by-case analysis for different types of inputs or
  conditions and showing that the code behaves correctly in each case.
  There is also a bit of an art to reducing what seem like numerous or
  even infinitely many cases to a small, manageable set of cases by
  spotting structure, such as symmetries, invariants or natural
  partitions of the input space.  Alternatively, one can look for a
  simpler argument that covers all cases.  These are techniques we
  employ routinely in mathematics and I think that kind of thinking
  and reasoning is quite valuable in software development too.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/my-lobsters-interview.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/mathematics.html&quot;&gt;#mathematics&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Miller-Rabin Speed Test</title>
    <link href="https://susam.net/code/web/miller-rabin-speed-test.html"/>
    <id>urn:uuid:35bf0b2b-2673-4ede-b7b4-75e8adebf666</id>
    <updated>2025-08-16T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  A demo page that implements the Miller-Rabin primality test to
  accurately detect primes for all numbers less than
  318665857834031151167461 and compare its speed against a simple
  division based primality test algorithm.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/web/miller-rabin-speed-test.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/html.html&quot;&gt;#html&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/mathematics.html&quot;&gt;#mathematics&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>Elliptical Python Programming</title>
    <link href="https://susam.net/elliptical-python-programming.html"/>
    <id>urn:uuid:9df19673-f0cf-459e-97cb-f300fe3100e8</id>
    <updated>2025-04-10T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  One thing I love about Python is how it comes with its very own
  built-in zen.  In moments of tribulations, when I am wrestling with
  crooked code and tangled thoughts, I often find solace in its
  timeless wisdom.  Here&apos;s a glimpse of the clarity it provides:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;$ &lt;kbd&gt;python3 -m this | grep e-&lt;/kbd&gt;
There should be one-- and preferably only one --obvious way to do it.&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  Indeed, there is one and only one &lt;em&gt;obvious&lt;/em&gt; way to write the
  number 1 in Python, like so:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;&amp;gt;&amp;gt;&amp;gt; &lt;kbd&gt;--(...==...)&lt;/kbd&gt;
1&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  You may, quite naturally, place several ones adjacently to produce
  larger integers:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;&amp;gt;&amp;gt;&amp;gt; &lt;kbd&gt;--(...==...)--(...==...)&lt;/kbd&gt;
2&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  And so on ad infinitum or until your heap collapses like a poorly
  made souffl&amp;eacute;.  Now, the &apos;pre-decrement operator&apos; at the
  beginning is entirely optional, much like the plus sign when you
  write &apos;+5 biscuits&apos; in a letter to your grandmother.  It&apos;s not
  wrong, but it is unnecessary.  So unless you want to look peculiar
  to your colleagues, you would likely want to adopt a more
  conventional style, such as this:
&lt;/p&gt;
&lt;pre&gt;&lt;samp&gt;&amp;gt;&amp;gt;&amp;gt; &lt;kbd&gt;(...==...)--(...==...)--(...==...)&lt;/kbd&gt;
3&lt;/samp&gt;&lt;/pre&gt;
&lt;p&gt;
  Now, all computer programs are, in some sense, just a long, earnest
  stream of bits.  It is currently fashionable to bundle these bits
  into groups of eight and write them as integers.  Following this
  trend, we can compute absolutely anything that is computable as long
  as we know exactly what integers to write.  Now, I wouldn&apos;t want to
  bore you with the finer details of computer science, not in this day
  and age, fascinating as they may be.  I trust you are quite capable
  of drawing the rest of the f... well, &lt;em&gt;feathered&lt;/em&gt;, nocturnal
  bird.  Once you&apos;ve grasped the basics, a typical first Python
  program might look something like this:
&lt;/p&gt;
&lt;pre class=&quot;wrap&quot;&gt;&lt;code&gt;exec(&apos;%c&apos;*((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))%(((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--(...==...),*(((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)),)*((...==...)--(...==...)),((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...))**((...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))--(...==...)))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Now you might be wondering if this is &lt;em&gt;really&lt;/em&gt; the way one
  ought to write production Python code.  Isn&apos;t it too much trouble to
  type those dots over and over again?  Not if you remap your
  &lt;kbd&gt;tab&lt;/kbd&gt; key to type three dots, of course.  But I understand
  not everyone likes to remap their keys like this.  In particular,
  there exists a peculiar species of mammal known to remap their
  &lt;kbd&gt;tab&lt;/kbd&gt; key to parentheses.  They claim it leads to
  enlightenment.  Such enlightened living forms may find the following
  program more convenient to type:
&lt;/p&gt;
&lt;pre class=&quot;wrap&quot;&gt;&lt;code&gt;exec(&apos;%c&apos;*((()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==()))%(((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==()))--((()==())--(()==())),((()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==())),((()==())--(()==())--(()==()))**((()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))--((()==())--(()==())),((()==())--(()==())--(()==())--(()==()))**((()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==()))--((()==())--(()==())--(()==())--(()==())),((()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==())--(()==())--(()==()))**((()==())--(()==()))--((()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==()))**((()==())--(()==()))--((()==())--(()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==()))**((()==())--(()==()))--(()==()),*(((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==()))**((()==())--(()==())--(()==())),)*((()==())--(()==())),((()==())--(()==())--(()==()))**((()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))--((()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==()))--((()==())--(()==())),((()==())--(()==()))**((()==())--(()==())--(()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==()))**((()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==()))--((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==())),((()==())--(()==())--(()==()))**((()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))--((()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==())--(()==())--(()==()))--((()==())--(()==())),((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==()))**((()==())--(()==())--(()==())),((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==()))**((()==())--(()==())),((()==())--(()==())--(()==())--(()==())--(()==())--(()==()))**((()==())--(()==()))--((()==())--(()==())--(()==())),((()==())--(()==()))*((()==())--(()==())--(()==())--(()==()))*((()==())--(()==())--(()==())--(()==())--(()==()))--(()==())))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  This program is functionally equivalent to the earlier one.  But
  Python isn&apos;t meant for enlightenment.  It&apos;s meant for getting things
  done.  And to get things done, code should be readable, maintainable
  and ideally not resemble an ancient summoning ritual.  That&apos;s why I
  personally prefer the earlier style, the one with the ellipses.  It
  gracefully avoids the disconcerting void that lurks within the
  parentheses.  After all, programs must be written for people to read
  and only incidentally for machines to execute.
&lt;/p&gt;
&lt;p&gt;
  Finally, I must emphasise that you should never deploy code like
  this in production.  If you plan to write code like this for your
  production CGI scripts, I implore you to add some ellipses for
  logging.  When dung inevitably collides with the fan, you&apos;ll be
  immensely glad you scattered some useful logs amidst the ellipses
  that hold together your business logic.  With that small piece of
  unsolicited advice, I&apos;ll end this brief distraction from scrolling
  through endless arguments on Internet forums.  Happy coding and may
  your parentheses stay balanced (and may your ellipses be the
  punctuation that ...
  &lt;!-- ).  Now, I can sleep peacefully!  --&gt;
&lt;/p&gt;
&lt;!--
  For the enlightened folks who are offended by the penultimate
  paragraph, lighten up!  I am no stranger to the fine art of nested
  parentheses.  This very blog is crafted with an abundance of them,
  all forged through the mighty steel and bank machinery.
--&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/elliptical-python-programming.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/absurd.html&quot;&gt;#absurd&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/python.html&quot;&gt;#python&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/humour.html&quot;&gt;#humour&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
  <entry>
    <title>MathB 1.3.0</title>
    <link href="https://susam.net/code/news/mathb/1.3.0.html"/>
    <id>urn:uuid:d5b77157-42a6-4423-9c96-50c9151dc542</id>
    <updated>2025-03-16T00:00:00Z</updated>
    <content type="html">
<!-- BEGIN HTML -->
&lt;p&gt;
  MathB 1.3.0 is likely the final update of MathB, a web-based
  mathematics pastebin service.  The online mathematics pastebin
  service previously hosted at &lt;code&gt;https://mathb.in/&lt;/code&gt; has been
  shut down today after 13 years of continuous service.  See the post
  &lt;a href=&quot;../../../mathbin-is-shutting-down.html&quot;&gt;MathB.in Is
  Shutting Down&lt;/a&gt; for more details about this.  This release
  captures the state of this project as it was today at the time of
  shutting down the online service.
&lt;/p&gt;
&lt;p&gt;
  This update includes a few new features.  For example, there is now
  a configurable runtime property named &lt;code&gt;:expect&lt;/code&gt; that can
  be used to enforce the presence of certain tokens in the posts
  submitted by the users.  This feature was used to enforce the
  presence of LaTeX delimiters in the online service, a measure that
  was instrumental in reducing spam to a great extent in the last few
  years.  There are a few other minor changes too.
  See &lt;a href=&quot;https://github.com/susam/mathb/blob/main/CHANGES.md&quot;&gt;CHANGES.md&lt;/a&gt;
  for a detailed changelog.
&lt;/p&gt;
&lt;p&gt;
  The source code of MathB remains free and open-source, so anyone
  interested in hosting their own instance can still do so.  To access
  the source code of MathB, please visit
  &lt;a href=&quot;http://github.com/susam/mathb&quot;&gt;github.com/susam/mathb&lt;/a&gt;.
&lt;/p&gt;
<!-- ### -->
&lt;p&gt;
  &lt;a href="https://susam.net/code/news/mathb/1.3.0.html"&gt;Read on website&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/web.html&quot;&gt;#web&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/programming.html&quot;&gt;#programming&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/technology.html&quot;&gt;#technology&lt;/a&gt; |
  &lt;a href=&quot;https://susam.net/tag/release.html&quot;&gt;#release&lt;/a&gt;
&lt;/p&gt;
<!-- END HTML -->
    </content>
  </entry>
</feed>
