summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--article.js81
-rw-r--r--fib.html41
-rw-r--r--forgejno.html34
-rw-r--r--no_ai.html80
-rw-r--r--prose.css96
-rw-r--r--reversible.html242
-rw-r--r--reversible.rs158
-rw-r--r--soplt.html17
-rw-r--r--stars.js1
-rw-r--r--style.css5
-rw-r--r--writing.html22
11 files changed, 628 insertions, 149 deletions
diff --git a/article.js b/article.js
index 4818a3d..723d97f 100644
--- a/article.js
+++ b/article.js
@@ -1,53 +1,52 @@
+function addHeader() {
+ const header = document.querySelector("header");
+
+ const h1 = document.createElement('h1');
+ h1.textContent = document.title;
+ header.appendChild(h1);
+
+ const time = document.createElement('time');
+ const metaDate = document.querySelector('meta[property="article:published_time"]');
+ const dateString = metaDate.getAttribute('content');
+ const displayDate = (new Date(dateString)).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ timeZone: 'UTC'
+ });
+ time.setAttribute('datetime', dateString);
+ time.textContent = displayDate;
+ header.appendChild(time);
+
+ const hr = document.createElement('hr');
+ header.appendChild(hr);
+}
+
function addTOC() {
- var tocDiv = document.createElement("div");
- tocDiv.classList += "toc";
- var toc = document.createElement("ul");
+ const tocDiv = document.getElementById("toc");
+ if (!tocDiv) return;
- document.body.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
- var link = document.createElement('a');
- link.setAttribute('href', '#'+heading.id.toLowerCase());
- link.textContent = heading.textContent;
-
- var li = document.createElement('li');
- li.appendChild(link);
-
- toc.appendChild(li);
- });
+ const tocHeading = document.createElement("h2");
+ tocHeading.textContent = "Table of Contents";
+ tocDiv.appendChild(tocHeading);
- tocDiv.append(toc);
- document.body.prepend(tocDiv);
+ const toc = document.createElement("ul");
- var styleBlock = document.createElement("style");
- styleBlock.textContent = `
- .toc {
- position: fixed;
- width: 17rem;
- top: 32px;
- left: calc(50vw - 37.5ch - 20rem);
- padding-right: 0.75rem;
- z-index: 1;
+ document.querySelectorAll('article :is(h1, h2, h3, h4, h5, h6):not(header *, #toc *)').forEach(heading => {
+ const link = document.createElement("a");
+ link.href = "#" + heading.id.toLowerCase();
+ link.textContent = heading.textContent;
- ul {
- margin: 0;
- padding-left: 1rem;
- line-height: 1.4;
- }
+ const li = document.createElement("li");
+ li.appendChild(link);
- li {
- margin-bottom: 0.35rem;
- font-size: 1.1rem;
- }
- }
+ toc.appendChild(li);
+ });
- @media (max-width: 1000px) {
- .toc {
- display: none;
- }
- }
- `;
- document.head.append(styleBlock);
+ tocDiv.appendChild(toc);
}
document.addEventListener("DOMContentLoaded", () => {
+ addHeader()
addTOC()
}) \ No newline at end of file
diff --git a/fib.html b/fib.html
index 1d38013..998308e 100644
--- a/fib.html
+++ b/fib.html
@@ -2,35 +2,13 @@
<html>
<head>
<title>I accidentally derived a formula for the Fibonacci Sequence</title>
+ <meta property="article:published_time" content="2026-06-11">
<link rel="stylesheet" href="prose.css">
- <style>
- table {
- border-collapse: collapse;
- margin: 0 auto;
- th {
- color: #e6e6e6;
- text-align: center;
- border: solid;
- padding: 0 1.5ch;
- border-width: 0 0 1px 0;
-
- &[scope="row"] {
- border-width: 0 1px 0 0;
- }
- }
- td {
- color: #00ff78;
- text-align: center;
- padding: 0 1.5ch;
- }
- }
- </style>
+ <script src="article.js"></script>
</head>
<body>
- <div class="content">
- <h1>I accidentally derived a formula for the Fibonacci Sequence</h1>
- <p class="comment">Published June 11, 2026</p>
- <hr>
+ <article>
+ <header></header>
<p>Last night my brother pitched me this math problem over dinner (which he later revealed came from <a href="https://www.youtube.com/watch?v=XeOTNEb-QsM">this video</a>). It goes something like this:</p>
<blockquote>
@@ -71,7 +49,14 @@
<p>Next, since I had no idea how I would go about counting the sequences, I looked at how I could group them, in hoping that would make them easier to count.</p>
<p>Arbitrarily, I decided to group the sequences by how many 2s were in them.</p>
<p>For the 5-stair staircase example, all of the possible sequences of 1s and 2s that sum to 5 look like:</p>
- <code>[1 1 2 1]<br>[2 1 2]<br>[1 2 1 1]<br>[1 1 1 1 1]<br>[2 1 1 1]<br>[1 2 2]<br>[1 1 1 2]<br>[2 2 1]</code>
+ <pre><code>[1 1 2 1]
+[2 1 2]
+[1 2 1 1]
+[1 1 1 1 1]
+[2 1 1 1]
+[1 2 2]
+[1 1 1 2]
+[2 2 1]</code></pre>
<p>Grouping each sequence by the number of 2s in it yields the table:</p>
<table>
<tbody>
@@ -313,6 +298,6 @@
</mrow>
</math>
<p>This is the exact same function that appears on ProofWiki!</p>
- </div>
+ </article>
</body>
</html> \ No newline at end of file
diff --git a/forgejno.html b/forgejno.html
index a8a970c..e36b358 100644
--- a/forgejno.html
+++ b/forgejno.html
@@ -2,23 +2,27 @@
<html>
<head>
<title>You don't need Forgejo</title>
+ <meta property="article:published_time" content="2026-05-03">
<link rel="stylesheet" href="prose.css">
<script src="article.js"></script>
</head>
<body>
- <div class="content">
- <h1 id="title">You don't need Forgejo</h1>
- <p class="comment">published May 3, 2026</p>
- <hr>
+ <article>
+ <header></header>
- <p><a href="https://mitchellh.com/writing/ghostty-leaving-github">People</a>
- <a href="https://vinyl-cache.org/organization/moving.html">ditching</a>
- GitHub after <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability">recent issues</a>
- are looking to self-hosting Forgejo as an alternative.</p>
+ <p>
+ <a href="https://mitchellh.com/writing/ghostty-leaving-github">People</a>
+ <a href="https://vinyl-cache.org/organization/moving.html">ditching</a>
+ GitHub after
+ <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability">recent issues</a>
+ are looking to self-hosting Forgejo as an alternative.
+ </p>
<p>Despite some (poorly disclosed) <a href="https://dustri.org/b/carrot-disclosure-forgejo.html">security issues</a>, I think there's a more fundemental reason why you shouldn't use Forgejo.</p>
<p>If you're just a single person trying to publish your independent coding projects, why do you need: CI, Issue Tracking, Pull Requests, Multi-user account management, etc?</p>
<p>just use <a href="https://git.zx2c4.com/cgit">cgit</a>.</p>
+ <div id="toc"></div>
+
<h2 id="forge">How do git forges (like GitHub) even work?</h2>
<p>A git forge is basically just a directory of git repositories, served over http or ssh, with a fancy web frontend.</p>
<p>On the server, since the repos don't need to store mutable state (local commits, staged files, etc) they're stored in a special format that's basically just the <code>.git</code>
@@ -32,10 +36,10 @@
<h2 id="guide">cgit Setup Guide</h2>
<p>The cgit setup process is fairly involved, but if you were planning on self-hosting Forgejo, then you can handle it.</p>
- <p class="comment">I'll be using Caddy as the webserver for this setup, but I'll link tutorials for NGINX and Apache if prefer xml configuration files and manual https certificates ;&rpar;</p>
+ <p class="comment">I'll be using Caddy as the webserver for this setup, but this could be modified for NGINX and Apache if you prefer xml configuration files and manual https certificates ;&rpar;</p>
<p>To keep everything organized, I'll run cgit under a user called "git". Our git repos will be stored in this directory.</p>
- <code>$ useradd -r -d /var/lib/cgit -s /usr/bin/git-shell git</code>
+ <pre><code>$ useradd -r -d /var/lib/cgit -s /usr/bin/git-shell git</code></pre>
<h3 id="cfg-fcgiwrap">fcgiwrap</h3>
<p>cgit is served as a <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">cgi application</a>.</p>
@@ -45,7 +49,7 @@
<ol>
<li><p>Install fcgiwrap with a package manager. This will create two systemd files: fcgiwrap.service and fcgiwrap.socket.</p></li>
<li><p>Open fcgiwrap.service in a text editor (<code>$ systemctl edit fcgiwrap.service</code>) and set the User and Group to be our "git" user.</p></li>
- <li><p>Open fcgiwrap.socket (<code>$ systemctl edit fcgiwrap.socket</code>) and add "SocketUser=caddy" and "SocketGroup=caddy" under the [Socket] section. (replace with the correct Caddy user for your system)</p></li>
+ <li><p>Open fcgiwrap.socket (<code>$ systemctl edit fcgiwrap.socket</code>) and add "SocketUser=caddy" and "SocketGroup=caddy" under the <code>[Socket]</code> section. (replace with the correct Caddy user for your system)</p></li>
</ol>
<h3 id="cfg-cgit">cgit</h3>
@@ -108,8 +112,10 @@ snapshots=tar.gz zip</code></pre>
<p>In return, you get a tiny stack, fewer moving parts, lower attack surface, lower resource use, and a setup you can understand.</p>
<p>For my personal projects, that trade is acceptable.</p>
- <hr>
- <p class="comment">Discuss on <a href="https://bsky.app/profile/cwilliams1221.bsky.social/post/3mkyog3enls2w">Bluesky</a></p>
- </div>
+ <footer>
+ <hr>
+ <p class="comment">Discuss on <a href="https://bsky.app/profile/cwilliams1221.bsky.social/post/3mkyog3enls2w">Bluesky</a></p>
+ </footer>
+ </article>
</body>
</html>
diff --git a/no_ai.html b/no_ai.html
index 3a4cabe..3a142a0 100644
--- a/no_ai.html
+++ b/no_ai.html
@@ -1,30 +1,58 @@
<!DOCTYPE html>
<html>
-<head>
- <title>Taking a break</title>
- <link rel="stylesheet" href="prose.css">
- <script src="article.js"></script>
-</head>
-<body>
- <div class="content">
- <p>Prolonged access to LLMs is making me stupid. I can't code as fluently as I used to. (and I'm <a href="https://larsfaye.com/articles/agentic-coding-is-a-trap">not</a> the only one)</p>
- <p>
- I experienced the drunkening power from the speed at which development is possible with Claude Code (I built an Emacs clone in a day; it was amazing).
- But when the tokens ran out, I felt the overwhelming discomfort of not having Claude's help when starting a new project. That feeling was a wakeup call.
- </p>
- <p>
- At heart, I am a programmer. Not a manager, and not the CEO of a multi-million dollar corperation. I don't require ultimate productivity and perfection.
- To me, what matters most is familiarity with the tools and techniques (languages, libraries, frameworks), and bossing around agents all day does not give me those skils.
- </p>
+ <head>
+ <title>Taking a break</title>
+ <link rel="stylesheet" href="prose.css">
+ <script src="article.js"></script>
+ </head>
+ <body>
+ <article>
+ <p>
+ Prolonged access to LLMs is making me stupid.
+ I can't code as fluently as I used to.
+ (and I'm <a href="https://larsfaye.com/articles/agentic-coding-is-a-trap">not</a> the only one)
+ </p>
+ <p>
+ I experienced the drunkening power from the speed at which development is possible with Claude Code (I built an Emacs clone in a day; it was amazing).
+ But when the tokens ran out, I felt the overwhelming discomfort of not having Claude's help when starting a new project.
+ That feeling was a wakeup call.
+ </p>
+ <p>
+ At heart, I am a programmer.
+ Not a manager, and not the CEO of a multi-million dollar corperation.
+ I don't require ultimate productivity and perfection.
+ To me, what matters most is familiarity with the tools and techniques (languages, libraries, frameworks), and bossing around agents all day does not give me those skils.
+ </p>
- <h2 id="not">What this isn't</h2>
- <p>I don't hate LLMs. In fact, I still see them a very impressive tool! I love to see people building awesome things, and I think AI empowers more builders than any other piece of technology. I'm not <a href="https://www.antirez.com/news/158">"falling for the anti-AI hype"</a></p>
- <p>People who believe that AI is the right tool for their workflow should keep using it. And who knows? Maybe I'll continue vibecoding in the future. I just don't think AI is the right tool for me, for now.</p>
+ <h2 id="not">What this isn't</h2>
+ <p>
+ I don't hate LLMs.
+ In fact, I still see them a very impressive tool!
+ I love to see people building awesome things, and I think AI empowers more builders than any other piece of technology.
+ I'm not <a href="https://www.antirez.com/news/158">"falling for the anti-AI hype"</a>
+ </p>
+ <p>
+ People who believe that AI is the right tool for their workflow should keep using it.
+ And who knows? Maybe I'll continue vibecoding in the future.
+ I just don't think AI is the right tool for me, for now.
+ </p>
- <h2 id="goals">Taking a Break</h2>
- <p>So I'm taking a break. I'm uninstalling OpenCode and Pihole-ing ChatGPT. My <a href="./soplt.html">programming language project</a> this summer will be 0% vibecoded.</p>
- <p>Is it a good idea? I don't even know. How long will it last? I'm not really sure.</p>
- <p>All I know is that I never want to feel out of place without the help of an LLM ever again. And hopefully this is one way of achieving that.</p>
- </div>
-</body>
-</html> \ No newline at end of file
+ <h2 id="goals">Taking a Break</h2>
+ <p>
+ So I'm taking a break.
+ I'm uninstalling OpenCode and Pihole-ing ChatGPT.
+ My <a href="./soplt.html">programming language project</a> this summer will be 0% vibecoded.
+ </p>
+ <p>
+ Is it a good idea?
+ I don't even know.
+ How long will it last?
+ I'm not really sure.
+ </p>
+ <p>
+ All I know is that I never want to feel out of place without the help of an LLM ever again.
+ And hopefully this is one way of achieving that.
+ </p>
+ </article>
+ </body>
+</html>
diff --git a/prose.css b/prose.css
index 9c812fd..bc0ad5d 100644
--- a/prose.css
+++ b/prose.css
@@ -1,9 +1,12 @@
@import url('https://fonts.googleapis.com/css2?family=Gelasio:ital,wght@0,400..700;1,400..700&display=swap');
@import url('style.css');
-.content {
+article {
width: 100%;
max-width: 75ch;
+ margin: 0 auto;
+ position: relative;
+ z-index: 1;
}
p {
@@ -15,33 +18,27 @@ p {
a {
color: #4DA3FF;
- transition: color 0.2s ease, background-color 0.2s ease;
- border-radius: 3px;
+ text-decoration: none;
&:hover {
- background-color: #4DA3FF;
- color: #121212;
- text-decoration: none;
+ text-decoration: underline;
}
}
-code {
- font-family: 'Roboto Mono';
- font-size: 0.9rem;
- color: #00ff78;
-}
-
li {
+ color: #e6e6e6;
font-family: 'Gelasio';
-}
+ line-height: 1.3;
+ font-size: 1.1rem;
-li::marker {
- color: #e6e6e6;
+ &::marker {
+ color: #e6e6e6;
+ }
}
h1 {
color: #e6e6e6;
- line-height: 1;
+ line-height: 1.15;
margin-bottom: 0;
font-family: 'Gelasio';
font-weight: 600;
@@ -65,8 +62,24 @@ h3 {
font-weight: 600;
}
-p.comment {
+.comment {
+ color: #888;
+}
+
+hr {
+ display: block;
+ height: 2px;
+ border: 0;
+ border-top: 2px solid #888;
+ margin: 1em 0;
+ padding: 0;
+}
+
+time {
+ display: block;
+ font-family: 'Gelasio';
color: #888;
+ margin-top: 0.5rem;
}
@media (max-width: 1000px) {
@@ -89,6 +102,55 @@ summary {
}
}
+table {
+ border-collapse: collapse;
+ margin: 0 auto;
+ th {
+ color: #e6e6e6;
+ text-align: center;
+ border: solid;
+ padding: 0 1.5ch;
+ border-width: 0 0 1px 0;
+
+ &[scope="row"] {
+ border-width: 0 1px 0 0;
+ }
+ }
+ td {
+ color: #00ff78;
+ text-align: center;
+ padding: 0 1.5ch;
+ }
+}
+
code .path {
color: #888;
}
+
+/* inline code */
+code {
+ color: #00ff78;
+ font-family: 'Roboto Mono';
+}
+
+/* CODEBLOCKS */
+pre {
+ color: #00ff78;
+ font-family: 'Roboto Mono';
+
+ code {
+ color: #e6e6e6;
+ }
+
+ kw { color: #6cb8ff; } /* keywords */
+ ty { color: #4ec9b0; } /* types */
+ fn { color: #dcdcaa; } /* functions */
+ mac { color: #c586c0; } /* macros */
+ str { color: #ce9178; } /* strings */
+ num { color: #b5cea8; } /* numbers */
+ com { color: #7f848e; font-style: italic; }
+ prop { color: #9cdcfe; } /* fields */
+ mod { color: #e6e6e6; } /* modules */
+ id { color: inherit; } /* local variables */
+}
+
diff --git a/reversible.html b/reversible.html
new file mode 100644
index 0000000..378045f
--- /dev/null
+++ b/reversible.html
@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <title>Reversible Programming Blew my Mind (Reversible Part 1)</title>
+ <meta property="article:published_time" content="2026-07-05">
+ <link rel="stylesheet" href="prose.css">
+ <script src="./article.js"></script>
+</head>
+
+<body>
+ <article>
+ <header></header>
+
+ <p>
+ Okay, a few days ago I learned about <a href="https://en.wikipedia.org/wiki/Reversible_programming_language">Reversible Programming</a>.
+ I decided to write a little Rust DSL to implement some of the stuff I learned.
+ In this article I walk through the code for my implementation and talk about some of the stuff you could do with
+ it.
+ </p>
+
+ <div id="toc"></div>
+
+ <h2 id="math">Math basis</h2>
+ <p>So remember in math class when you learned to take the inverse of a function?</p>
+ <p>For example, say you have <code>y = 3*x + 6</code>.</p>
+ <p>You can take the inverse by solving for x. In this case, the inverse of our function is <code>x = y/3 - 2</code>.</p>
+ <p>The idea behind reversible programming is that there's a specific way to write your code such that it can <strong>automatically compute the inverse</strong>.</p>
+ <p>This has some wild implications:</p>
+ <ul>
+ <li>Writing a parser automatically gives you a pretty-printer implementation</li>
+ <li>Database migrations are only <em>half</em> as scary (writing your <code>up</code> script automatically gives you your <code>down</code> script)</li>
+ <li>Writing operations on an image (ex. Photoshop clone) gives you free undo and redo.</li>
+ </ul>
+ <p>
+ These are just a few of the crazy things you could <em>theoretically</em> do with Reversible Programming.
+ But how does it actually work?
+ </p>
+
+ <h2 id="idea">Idea of the implementation</h2>
+ <p>
+ The idea behind this is that we can build small blocks of computation that we manually code the inverse logic for.
+ Then we use higher-level combinators to compose these small blocks together.
+ As long as the building-blocks are reversible and the combinators are implemented correctly, we'll have a fully reversible program.
+ </p>
+
+ <h2 id="foundations">Foundations</h2>
+ <p>As any Rust project starts, we'll define some shared behavior that our initial blocks of computation must implement.</p>
+ <pre><code><kw>trait</kw> <ty>Rev</ty> {
+ <kw>type</kw> <ty>Input</ty>;
+ <kw>type</kw> <ty>Output</ty>;
+
+ <kw>fn</kw> <fn>forward</fn>(&<kw>self</kw>, <id>i</id>: <kw>Self</kw>::<ty>Input</ty>) -> <kw>Self</kw>::<ty>Output</ty>;
+ <kw>fn</kw> <fn>reverse</fn>(&<kw>self</kw>, <id>o</id>: <kw>Self</kw>::<ty>Output</ty>) -> <kw>Self</kw>::<ty>Input</ty>;
+}</code></pre>
+
+ <p>
+ As a nice starter, let's write a block that would compute addition.
+ We can model this with a struct and an impl of the <code>Rev</code> trait.
+ </p>
+ <pre><code><kw>struct</kw> <ty>Addition</ty>&lt;<ty>A</ty>&gt; {
+ <prop>amount</prop>: <ty>A</ty>
+}
+
+<kw>impl</kw>&lt;<ty>A</ty>&gt; <ty>Rev</ty> <kw>for</kw> <ty>Addition</ty>&lt;<ty>A</ty>&gt;
+<kw>where</kw>
+ <ty>A</ty>: <mod>std</mod>::<mod>ops</mod>::<ty>Add</ty>&lt;<ty>Output</ty> = <ty>A</ty>&gt;
+ + <mod>std</mod>::<mod>ops</mod>::<ty>Sub</ty>&lt;<ty>Output</ty> = <ty>A</ty>&gt;
+ + <ty>Copy</ty>
+{
+ <kw>type</kw> <ty>Input</ty> = <ty>A</ty>;
+ <kw>type</kw> <ty>Output</ty> = <ty>A</ty>;
+
+ <kw>fn</kw> <fn>forward</fn>(&self, <id>a</id>: <ty>A</ty>) -> <ty>A</ty> {
+ <id>a</id> + <id>self</id>.<prop>amount</prop>
+ }
+
+ <kw>fn</kw> <fn>reverse</fn>(&self, <id>b</id>: <ty>A</ty>) -> <ty>A</ty> {
+ <id>b</id> - <id>self</id>.<prop>amount</prop>
+ }
+}</code></pre>
+
+ <p>
+ While the generics might look scary, this is just saying "the input and output can be any type that you can add and subtract with."
+ Honestly these are light work compared to what's coming up.
+ </p>
+ <p>Oh, and it will look nicer if we have an easy way to construct an <code>Addition</code> block:</p>
+ <pre><code><kw>fn</kw> <fn>plus</fn>&lt;<ty>A</ty>&gt;(<id>amount</id>: <ty>A</ty>) -&gt; <ty>Addition</ty>&lt;<ty>A</ty>&gt; {
+ <ty>Addition</ty> { <prop>amount</prop> }
+}</code></pre>
+
+ <p>So let's see it in action!</p>
+ <pre><code class="language-rust"><kw>fn</kw> <fn>main</fn>() {
+ <kw>let</kw> <id>operation</id> = <fn>plus</fn>(<num>5</num>);
+
+ <mac>println!</mac>(<str>"{}"</str>, <id>operation</id>.<fn>forward</fn>(<num>6</num>)); <com>// prints 11 (6 + 5 = 11)</com>
+ <mac>println!</mac>(<str>"{}"</str>, <id>operation</id>.<fn>reverse</fn>(<num>11</num>)); <com>// prints 6 (11 - 5 = 6)</com>
+}</code></pre>
+
+ <p>Obviously nothing special so far. We're just using the inverse of addition, which is subtraction, just like our
+ trait impl said.</p>
+ <p>I've implemented a few more in the <a href="./reversible.rs#:~:text=%2F%2F%20more%20blocks">source code</a>:</p>
+ <ul>
+ <li><code>neg()</code> negates a number</li>
+ <li><code>times(x)</code> multiplies a number by x</li>
+ <li><code>discrete(x: Vec&lt;(A, B)&gt;)</code> creates a reversible block out of a set of discrete inputs and outputs.</li>
+ </ul>
+
+ <h2 id="first">First Combinators</h2>
+ <p>
+ The first combinator we'll implement is <code>Pipe</code>.
+ This takes in two <code>Rev</code>s and and makes one that applies them in sequential order.
+ </p>
+ <pre><code><kw>struct</kw> <ty>Pipe</ty>&lt;<ty>X</ty>, <ty>Y</ty>&gt;(<ty>X</ty>, <ty>Y</ty>);
+<kw>impl</kw>&lt;<ty>X</ty>, <ty>Y</ty>&gt; <ty>Rev</ty> <kw>for</kw> <ty>Pipe</ty>&lt;<ty>X</ty>, <ty>Y</ty>&gt;
+<kw>where</kw>
+ <ty>X</ty>: <ty>Iso</ty>,
+ <ty>Y</ty>: <ty>Iso</ty>&lt;<ty>Input</ty> = <ty>X</ty>::<ty>Output</ty>&gt;
+{
+ <kw>type</kw> <ty>Input</ty> = <ty>X</ty>::<ty>Input</ty>;
+ <kw>type</kw> <ty>Output</ty> = <ty>Y</ty>::<ty>Output</ty>;
+
+ <kw>fn</kw> <fn>forward</fn>(&<kw>self</kw>, <id>a</id>: <kw>Self</kw>::<ty>Input</ty>) -&gt; <kw>Self</kw>::<ty>Output</ty> {
+ <kw>self</kw>.<num>1</num>.<fn>forward</fn>(<kw>self</kw>.<num>0</num>.<fn>forward</fn>(<id>a</id>))
+ }
+
+ <kw>fn</kw> <fn>reverse</fn>(&<kw>self</kw>, <id>c</id>: <kw>Self</kw>::<ty>Output</ty>) -&gt; <kw>Self</kw>::<ty>Input</ty> {
+ <kw>self</kw>.<num>0</num>.<fn>reverse</fn>(<kw>self</kw>.<num>1</num>.<fn>reverse</fn>(<id>c</id>))
+ }
+}</code></pre>
+
+ <p>
+ Additionally, we'll implement <code>Inverse</code> (not to be confused with <code>.reverse()</code>).
+ This flips the forward and reverse methods.
+ </p>
+ <pre><code><kw>struct</kw> <ty>Inverse</ty>&lt;<ty>X</ty>&gt;(<ty>X</ty>);
+<kw>impl</kw>&lt;<ty>X</ty>: <ty>Iso</ty>&gt; <ty>Rev</ty> <kw>for</kw> <ty>Inverse</ty>&lt;<ty>X</ty>&gt; {
+ <kw>type</kw> <ty>Input</ty> = <ty>X</ty>::<ty>Output</ty>;
+ <kw>type</kw> <ty>Output</ty> = <ty>X</ty>::<ty>Input</ty>;
+
+ <kw>fn</kw> <fn>forward</fn>(&<kw>self</kw>, <id>a</id>: <kw>Self</kw>::<ty>Input</ty>) -&gt; <kw>Self</kw>::<ty>Output</ty> {
+ <kw>self</kw>.<num>0</num>.<fn>reverse</fn>(<id>a</id>)
+ }
+
+ <kw>fn</kw> <fn>reverse</fn>(&<kw>self</kw>, <id>b</id>: <kw>Self</kw>::<ty>Output</ty>) -&gt; <kw>Self</kw>::<ty>Input</ty> {
+ <kw>self</kw>.<num>0</num>.<fn>forward</fn>(<id>b</id>)
+ }
+}</code></pre>
+
+ <p>
+ And finally, it will look nicer if we can use a chaining syntax to apply these combinators.
+ We'll do that using an extension trait:
+ </p>
+ <pre><code><kw>trait</kw> <ty>RevExt</ty>: <ty>Rev</ty> + <ty>Sized</ty> {
+ <kw>fn</kw> <fn>then</fn>&lt;<ty>N</ty>: <ty>Rev</ty>&lt;<ty>Input</ty> = <kw>Self</kw>::<ty>Output</ty>&gt;&gt;(<kw>self</kw>, <id>other</id>: <ty>N</ty>) -&gt; <ty>Pipe</ty>&lt;<kw>Self</kw>, <ty>N</ty>&gt; {
+ <ty>Pipe</ty>(<kw>self</kw>, <id>other</id>)
+ }
+
+ <kw>fn</kw> <fn>inverse</fn>(<kw>self</kw>) -&gt; <ty>Inverse</ty>&lt;<kw>Self</kw>&gt; {
+ <ty>Inverse</ty>(<kw>self</kw>)
+ }
+}
+<kw>impl</kw>&lt;<ty>T</ty>: <ty>Rev</ty>&gt; <ty>RevExt</ty> <kw>for</kw> <ty>T</ty> {}</code></pre>
+
+ <h2 id="playground">Playing around</h2>
+ <p>
+ So, what can we do with these combinators?
+ Quite a lot, actually!
+ Our math example from the beginning is trivial now:
+ </p>
+ <pre><code><kw>fn</kw> <fn>main</fn>() {
+ <kw>let</kw> <id>y</id> = <fn>times</fn>(<num>3</num>).<fn>then</fn>(<fn>plus</fn>(<num>6</num>));
+ <kw>let</kw> <id>x</id> = <id>y</id>.<fn>inverse</fn>(); <com>// We don't have to know how this gets computed!</com>
+
+ <mac>println!</mac>(<str>"{}"</str>, <id>y</id>.<fn>forward</fn>(<num>8</num>)); <com>// prints 30</com>
+ <mac>println!</mac>(<str>"{}"</str>, <id>x</id>.<fn>forward</fn>(<num>30</num>)); <com>// prints 8</com>
+}</code></pre>
+
+ <p>Here's a little celsius to farenheit calculator that only encodes the transformation one way:</p>
+ <pre><code><kw>fn</kw> <fn>main</fn>() {
+ <com>// F = (C * 1.8) + 32</com>
+ <kw>let</kw> <id>celsius_to_fahrenheit</id> = <fn>times</fn>(<num>1.8</num>).<fn>then</fn>(<fn>add</fn>(<num>32.0</num>));
+
+ <mac>println!</mac>(<str>"{}"</str>, <id>celsius_to_fahrenheit</id>.<fn>forward</fn>(<num>0.0</num>)); <com>// prints 32.0</com>
+ <mac>println!</mac>(<str>"{}"</str>, <id>celsius_to_fahrenheit</id>.<fn>forward</fn>(<num>100.0</num>)); <com>// prints 212.0</com>
+
+ <mac>println!</mac>(<str>"{}"</str>, <id>celsius_to_fahrenheit</id>.<fn>reverse</fn>(<num>212.0</num>)); <com>// prints 100.0</com>
+
+ <kw>let</kw> <id>fahrenheit_to_celsius</id> = <id>celsius_to_fahrenheit</id>.<fn>inverse</fn>();
+ <mac>println!</mac>(<str>"{}"</str>, <id>fahrenheit_to_celsius</id>.<fn>forward</fn>(<num>32.0</num>)); <com>// prints 0.0</com>
+}</code></pre>
+
+ <h2 id="under">Under</h2>
+ <p>
+ <code>under</code> is a higher order combinator, which means it's implemented in terms of other combinators.
+ Under basically says "do a thing, do another thing, then undo the first thing.
+ With this foundation built up, we can implement it quite easily:
+ </p>
+ <pre><code><kw>fn</kw> <fn>under</fn>&lt;<ty>Op</ty>, <ty>Val</ty>&gt;(<id>op</id>: <ty>Op</ty>, <id>val</id>: <ty>Val</ty>) -&gt; <kw>impl</kw> <ty>Rev</ty>&lt;
+ <ty>Input</ty> = <ty>Op</ty>::<ty>Input</ty>,
+ <ty>Output</ty> = <ty>Op</ty>::<ty>Input</ty>
+&gt;
+<kw>where</kw>
+ <ty>Op</ty>: <ty>Rev</ty> + <ty>Clone</ty>,
+ <ty>Val</ty>: <ty>Rev</ty>&lt;<ty>Input</ty> = <ty>Op</ty>::<ty>Output</ty>, <ty>Output</ty> = <ty>Op</ty>::<ty>Output</ty>&gt;
+{
+ <id>op</id>.<fn>clone</fn>().<fn>then</fn>(<id>val</id>).<fn>then</fn>(<id>op</id>.<fn>inverse</fn>())
+}</code></pre>
+
+ <p>
+ We can use this to encode "setup operations."
+ For example, this code takes in a number string, and returns a number string, but does an operation on integers in-between.
+ </p>
+ <pre><code><kw>fn</kw> <fn>main</fn>() {
+ <kw>let</kw> <id>num_as_str</id> = <fn>discrete</fn>(<mac>vec!</mac>[
+ (<str>"one"</str>, <num>1</num>),
+ (<str>"two"</str>, <num>2</num>),
+ (<str>"three"</str>, <num>3</num>),
+ (<str>"four"</str>, <num>4</num>),
+ (<str>"five"</str>, <num>5</num>),
+ (<str>"six"</str>, <num>6</num>)
+ ]);
+
+ <kw>let</kw> <id>operation</id> = <fn>under</fn>(<id>num_as_str</id>, <fn>add</fn>(<num>3</num>));
+
+ <mac>println!</mac>(<str>"{}"</str>, <id>operation</id>.<fn>forward</fn>(<str>"three"</str>)); <com>// prints "six"</com>
+ <mac>println!</mac>(<str>"{}"</str>, <id>operation</id>.<fn>reverse</fn>(<str>"five"</str>)); <com>// prints "two"</com>
+}</code></pre>
+
+ <h2 id="next">Next Up</h2>
+ <p>
+ Well, that's all I've got for today.
+ Next time, we'll look at data-structures, like sum and product types, and maybe some other stuff.
+ The source code for this project so far is available <a href="./reversible.rs">here</a>.
+ Stay tuned!
+ </p>
+ </article>
+</body>
+
+</html> \ No newline at end of file
diff --git a/reversible.rs b/reversible.rs
new file mode 100644
index 0000000..19e6f98
--- /dev/null
+++ b/reversible.rs
@@ -0,0 +1,158 @@
+use std::marker::PhantomData;
+
+pub trait Rev {
+ type Input;
+ type Output;
+
+ fn forward(&self, i: Self::Input) -> Self::Output;
+ fn reverse(&self, o: Self::Output) -> Self::Input;
+}
+
+#[derive(Clone)]
+pub struct Addition<A> {
+ amount: A
+}
+impl<A> Rev for Addition<A>
+where
+ A: std::ops::Add<Output = A> + std::ops::Sub<Output = A> + Copy
+{
+ type Input = A;
+ type Output = A;
+
+ fn forward(&self, a: A) -> A {
+ a + self.amount
+ }
+
+ fn reverse(&self, b: A) -> A {
+ b - self.amount
+ }
+}
+pub fn plus<A>(amount: A) -> Addition<A> {
+ Addition { amount }
+}
+
+// more blocks
+
+#[derive(Clone)]
+pub struct Negate<A>(PhantomData<A>);
+impl<A: std::ops::Neg<Output = A>> Rev for Negate<A> {
+ type Input = A;
+ type Output = A;
+
+ fn forward(&self, a: Self::Input) -> Self::Output {
+ -a
+ }
+
+ fn reverse(&self, b: Self::Output) -> Self::Input {
+ -b
+ }
+}
+pub fn neg<A>() -> Negate<A> {
+ Negate(PhantomData)
+}
+
+#[derive(Clone)]
+pub struct Multiplication<A> {
+ amount: A
+}
+impl<A> Rev for Multiplication<A>
+where
+ A: std::ops::Mul<Output = A> + std::ops::Div<Output = A> + Copy,
+{
+ type Input = A;
+ type Output = A;
+
+ fn forward(&self, a: A) -> A {
+ a * self.amount
+ }
+
+ fn reverse(&self, b: A) -> A {
+ b / self.amount
+ }
+}
+pub fn times<A>(amount: A) -> Multiplication<A> {
+ Multiplication { amount }
+}
+
+#[derive(Clone)]
+pub struct DiscreteMap<A, B>(Vec<(A, B)>);
+impl<A: PartialEq + Clone, B: PartialEq + Clone> Rev for DiscreteMap<A, B> {
+ type Input = A;
+ type Output = B;
+
+ fn forward(&self, a: A) -> B {
+ for i in self.0.iter() {
+ if i.0 == a {
+ return i.1.clone()
+ }
+ }
+ panic!()
+ }
+
+ fn reverse(&self, b: B) -> A {
+ for i in self.0.iter() {
+ if i.1 == b {
+ return i.0.clone()
+ }
+ }
+ panic!()
+ }
+}
+pub fn discrete<A, B>(vec: Vec<(A, B)>) -> DiscreteMap<A, B> {
+ DiscreteMap(vec)
+}
+
+// combinators
+
+#[derive(Clone)]
+pub struct Pipe<X, Y>(X, Y);
+impl<X, Y> Rev for Pipe<X, Y>
+where
+ X: Rev,
+ Y: Rev<Input = X::Output>
+{
+ type Input = X::Input;
+ type Output = Y::Output;
+
+ fn forward(&self, a: Self::Input) -> Self::Output {
+ self.1.forward(self.0.forward(a))
+ }
+
+ fn reverse(&self, c: Self::Output) -> Self::Input {
+ self.0.reverse(self.1.reverse(c))
+ }
+}
+
+#[derive(Clone)]
+pub struct Inverse<X>(X);
+impl<X: Rev> Rev for Inverse<X> {
+ type Input = X::Output;
+ type Output = X::Input;
+
+ fn forward(&self, a: Self::Input) -> Self::Output {
+ self.0.reverse(a)
+ }
+
+ fn reverse(&self, b: Self::Output) -> Self::Input {
+ self.0.forward(b)
+ }
+}
+
+pub trait RevExt: Rev + Sized {
+ fn then<N: Rev<Input = Self::Output>>(self, other: N) -> Pipe<Self, N> {
+ Pipe(self, other)
+ }
+
+ fn inverse(self) -> Inverse<Self> {
+ Inverse(self)
+ }
+}
+impl<T: Rev> RevExt for T {}
+
+pub fn under<Op, Val>(op: Op, val: Val) -> impl Rev<Input = Op::Input, Output = Op::Input>
+where
+ Op: Rev + Clone,
+ Val: Rev<Input = Op::Output, Output = Op::Output>
+{
+ op.clone().then(val).then(op.inverse())
+} \ No newline at end of file
diff --git a/soplt.html b/soplt.html
index 0835c4b..a80c81e 100644
--- a/soplt.html
+++ b/soplt.html
@@ -2,14 +2,13 @@
<html>
<head>
<title>Summer of PLT</title>
+ <meta property="article:published_time" content="2026-05-10">
<link rel="stylesheet" href="prose.css">
<script src="article.js"></script>
</head>
<body>
- <div class="content">
- <h1 id="title">Summer of PLT</h1>
- <p class="comment">published May 10, 2026</p>
- <hr>
+ <article>
+ <header></header>
<p>Looking for a summer coding project? Try making a programming language!</p>
<p>I think that every programmer, new or experienced, could benefit from understanding a little bit about how the languages they use daily actually work.</p>
@@ -17,6 +16,8 @@
<p>I'm planning on doing something similar this summer, so I figured I'd invite other people to try a 'Summer of PLT' too.</p>
<p class="comment">Disclaimer: I'm just a guy with no formal education in PLT. Like <a href="https://danilafe.com/blog/i_love_programming_languages">Daniel Fedorin said</a>, I just <em>love the field of programming languages</em>.</p>
+ <div id="toc"></div>
+
<h2 id="read">Read a Book</h2>
<p>
If you're new to the field of programming languages, my first piece of advice is to read Crafting Interpreters!
@@ -87,8 +88,10 @@
<h2 id="contact">Contact</h2>
<p>Did I interest you in a 'Summer of PLT'? Let me know! I'd love to see some toy lisps this summer!</p>
- <hr>
- <p class="comment">Discuss on <a href="https://news.ycombinator.com/item?id=48087351">Hacker news</a> or <a href="https://bsky.app/profile/cwilliams1221.bsky.social/post/3mljk2zarjc2h">Bluesky</a></p>
- </div>
+ <footer>
+ <hr>
+ <p class="comment">Discuss on <a href="https://news.ycombinator.com/item?id=48087351">Hacker news</a> or <a href="https://bsky.app/profile/cwilliams1221.bsky.social/post/3mljk2zarjc2h">Bluesky</a></p>
+ </footer>
+ </article>
</body>
</html> \ No newline at end of file
diff --git a/stars.js b/stars.js
index 347d7c0..cb823ac 100644
--- a/stars.js
+++ b/stars.js
@@ -40,6 +40,7 @@ document.addEventListener("DOMContentLoaded", () => {
position: fixed;
inset: 0;
pointer-events: none;
+ z-index: 0;
}
.star {
diff --git a/style.css b/style.css
index 0172983..19c33e7 100644
--- a/style.css
+++ b/style.css
@@ -6,15 +6,12 @@
html, body {
background-color: #121212;
- margin: 0;
+ margin: 0 auto;
padding: 0;
min-height: 100vh;
}
body {
- display: flex;
- justify-content: center;
- align-items: flex-start;
padding-top: 2rem;
padding-left: 2rem;
padding-right: 2rem;
diff --git a/writing.html b/writing.html
index 83dcf5f..dcb6d52 100644
--- a/writing.html
+++ b/writing.html
@@ -8,24 +8,22 @@
<link rel="stylesheet" href="prose.css">
<script src="stars.js"></script>
<style>
- body {
- align-items: center;
- }
-
- .content {
- width: fit-content;
+ article {
+ inset: 0;
+ margin: auto !important;
}
</style>
</head>
<body>
- <div class="content">
+ <article>
<h1>My Writing</h1>
<ul>
- <li><p><a href="./fib.html">I accidentally derived a formula for the Fibonacci Sequence</a></p></li>
- <li><p><a href="./no_ai.html">Taking a Break</a></p></li>
- <li><p><a href="./soplt.html">Summer of PLT</a></p></li>
- <li><p><a href="./forgejno.html">You Don't Need Forgejo</a></p></li>
+ <li><a href="./reversible.html">Reversible Programming Blew my Mind (Reversible Part 1)</a></li>
+ <li><a href="./fib.html">I accidentally derived a formula for the Fibonacci Sequence</a></li>
+ <li><a href="./no_ai.html">Taking a Break</a></li>
+ <li><a href="./soplt.html">Summer of PLT</a></li>
+ <li><a href="./forgejno.html">You Don't Need Forgejo</a></li>
</ul>
- </div>
+ </article>
</body>
</html>