diff options
| author | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-07-07 16:44:48 -0500 |
|---|---|---|
| committer | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-07-07 16:44:48 -0500 |
| commit | b5449eceedbd5d93ea4bd22e604fd972c46eda43 (patch) | |
| tree | 14f55e1f4414c5658fee42104f0d7ca07e8662e9 | |
| parent | af0a985afdbec0d526597e3cc90330ebca95d95d (diff) | |
| -rw-r--r-- | article.js | 81 | ||||
| -rw-r--r-- | fib.html | 41 | ||||
| -rw-r--r-- | forgejno.html | 34 | ||||
| -rw-r--r-- | no_ai.html | 80 | ||||
| -rw-r--r-- | prose.css | 96 | ||||
| -rw-r--r-- | reversible.html | 242 | ||||
| -rw-r--r-- | reversible.rs | 158 | ||||
| -rw-r--r-- | soplt.html | 17 | ||||
| -rw-r--r-- | stars.js | 1 | ||||
| -rw-r--r-- | style.css | 5 | ||||
| -rw-r--r-- | writing.html | 22 |
11 files changed, 628 insertions, 149 deletions
@@ -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 @@ -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 ;)</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 ;)</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> @@ -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> @@ -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><<ty>A</ty>> { + <prop>amount</prop>: <ty>A</ty> +} + +<kw>impl</kw><<ty>A</ty>> <ty>Rev</ty> <kw>for</kw> <ty>Addition</ty><<ty>A</ty>> +<kw>where</kw> + <ty>A</ty>: <mod>std</mod>::<mod>ops</mod>::<ty>Add</ty><<ty>Output</ty> = <ty>A</ty>> + + <mod>std</mod>::<mod>ops</mod>::<ty>Sub</ty><<ty>Output</ty> = <ty>A</ty>> + + <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><<ty>A</ty>>(<id>amount</id>: <ty>A</ty>) -> <ty>Addition</ty><<ty>A</ty>> { + <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<(A, B)>)</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><<ty>X</ty>, <ty>Y</ty>>(<ty>X</ty>, <ty>Y</ty>); +<kw>impl</kw><<ty>X</ty>, <ty>Y</ty>> <ty>Rev</ty> <kw>for</kw> <ty>Pipe</ty><<ty>X</ty>, <ty>Y</ty>> +<kw>where</kw> + <ty>X</ty>: <ty>Iso</ty>, + <ty>Y</ty>: <ty>Iso</ty><<ty>Input</ty> = <ty>X</ty>::<ty>Output</ty>> +{ + <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>) -> <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>) -> <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><<ty>X</ty>>(<ty>X</ty>); +<kw>impl</kw><<ty>X</ty>: <ty>Iso</ty>> <ty>Rev</ty> <kw>for</kw> <ty>Inverse</ty><<ty>X</ty>> { + <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>) -> <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>) -> <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><<ty>N</ty>: <ty>Rev</ty><<ty>Input</ty> = <kw>Self</kw>::<ty>Output</ty>>>(<kw>self</kw>, <id>other</id>: <ty>N</ty>) -> <ty>Pipe</ty><<kw>Self</kw>, <ty>N</ty>> { + <ty>Pipe</ty>(<kw>self</kw>, <id>other</id>) + } + + <kw>fn</kw> <fn>inverse</fn>(<kw>self</kw>) -> <ty>Inverse</ty><<kw>Self</kw>> { + <ty>Inverse</ty>(<kw>self</kw>) + } +} +<kw>impl</kw><<ty>T</ty>: <ty>Rev</ty>> <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><<ty>Op</ty>, <ty>Val</ty>>(<id>op</id>: <ty>Op</ty>, <id>val</id>: <ty>Val</ty>) -> <kw>impl</kw> <ty>Rev</ty>< + <ty>Input</ty> = <ty>Op</ty>::<ty>Input</ty>, + <ty>Output</ty> = <ty>Op</ty>::<ty>Input</ty> +> +<kw>where</kw> + <ty>Op</ty>: <ty>Rev</ty> + <ty>Clone</ty>, + <ty>Val</ty>: <ty>Rev</ty><<ty>Input</ty> = <ty>Op</ty>::<ty>Output</ty>, <ty>Output</ty> = <ty>Op</ty>::<ty>Output</ty>> +{ + <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 @@ -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 @@ -40,6 +40,7 @@ document.addEventListener("DOMContentLoaded", () => { position: fixed; inset: 0; pointer-events: none; + z-index: 0; } .star { @@ -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> |
