diff options
| author | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-08-16 15:21:39 -0500 |
|---|---|---|
| committer | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-08-16 15:21:39 -0500 |
| commit | eef357ebc10246da0da3c8bce427991eeb3f1dce (patch) | |
| tree | b7e5cdc91798a4d0395753640683d13f1a908fff | |
| parent | 32b19799a1355805bf69222ef1abb6d806980ced (diff) | |
add a logo, changes to css, format older articles
| -rw-r--r-- | Logo.svg | 18 | ||||
| -rw-r--r-- | article.js | 70 | ||||
| -rw-r--r-- | fib.html | 587 | ||||
| -rw-r--r-- | fonts.css | 37 | ||||
| -rw-r--r-- | fonts/FiraCode-VF.woff2 | bin | 0 -> 113088 bytes | |||
| -rw-r--r-- | fonts/FiraMath-Regular.woff2 | bin | 0 -> 99332 bytes | |||
| -rw-r--r-- | fonts/gelasio-latin-italic.woff2 | bin | 0 -> 40068 bytes | |||
| -rw-r--r-- | fonts/gelasio-latin.woff2 | bin | 0 -> 34832 bytes | |||
| -rw-r--r-- | forgejno.html | 1 | ||||
| -rw-r--r-- | minimalnetworkinglaptop.html | 212 | ||||
| -rw-r--r-- | no_ai.html | 1 | ||||
| -rw-r--r-- | prose.css | 169 | ||||
| -rw-r--r-- | reversible.html | 36 | ||||
| -rw-r--r-- | soplt.html | 1 | ||||
| -rw-r--r-- | style.css | 2 |
15 files changed, 648 insertions, 486 deletions
diff --git a/Logo.svg b/Logo.svg new file mode 100644 index 0000000..77eb306 --- /dev/null +++ b/Logo.svg @@ -0,0 +1,18 @@ +<svg version="1.1" + viewBox="0 0 960 960" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + + <rect + x="1" + y="1" + width="958" + height="958" + fill="none" + stroke="#e6e6e6" + stroke-width="40"/> + + <path + fill="#e6e6e6" + d="m311.93704 145.0l0 52.28067l120.27191 0l0 5.2159424c-26.282684 7.439789 -46.328003 19.367706 -60.135956 35.783752c-13.7127075 16.33519 -20.569061 36.18811 -20.569061 59.558716c0 29.839996 8.332367 53.69586 24.997131 71.567505c16.664764 17.871643 42.56656 30.527374 77.70538 37.967163l0 3.0325317c-42.18564 4.447693 -75.13425 18.235565 -98.845825 41.363586c-23.616333 23.04715 -35.4245 50.986816 -35.4245 83.81888c0 29.273987 5.856476 53.736267 17.569397 73.387024c11.712952 19.650757 30.710785 36.066833 56.99347 49.24817c26.282715 13.100464 61.659607 24.38147 106.13071 33.842896l66.706604 14.192261l-29.853699 94.614624l66.706665 14.1922l26.425537 -83.454956c1.1427002 -4.5286255 2.1425781 -8.895447 2.9996338 -13.100525c0.85705566 -4.205078 1.2855835 -8.046265 1.2855835 -11.52356c0 -12.938721 -4.6661377 -24.219727 -13.998413 -33.842957c-9.332275 -9.704041 -24.23529 -16.820374 -44.709167 -21.348938l-66.706635 -14.919983c-26.94928 -5.9033203 -48.756317 -13.1813965 -65.42105 -21.834167c-16.664764 -8.733704 -28.949066 -19.165527 -36.852936 -31.295654c-7.903839 -12.2109375 -11.855774 -26.524414 -11.855774 -42.94049l0 -10.431885c0 -19.89331 4.23761 -36.794586 12.71283 -50.703735c8.47522 -13.90918 22.235535 -24.583649 41.280975 -32.023438c19.04544 -7.4397583 43.756897 -11.159668 74.13434 -11.159668l76.41986 0l0 -52.159363l-76.41986 0c-30.948822 0 -54.803223 -7.075897 -71.5632 -21.227661c-16.664764 -14.2326355 -24.997131 -34.247253 -24.997131 -60.043915l0 -10.431885c0 -20.37854 4.380432 -37.643692 13.141357 -51.795456c8.85611 -14.151764 20.99759 -24.94754 36.424377 -32.38733c15.522034 -7.439789 33.805664 -11.159683 54.85086 -11.159683l116.84375 0l0 -52.28067z"/> +</svg>
\ No newline at end of file @@ -1,24 +1,63 @@ +function addMetadata() { + // Favicon + const favicon = document.createElement("link"); + favicon.rel = "icon"; + favicon.href = "/Logo.svg"; + favicon.type = "image/svg+xml"; + document.head.appendChild(favicon); + + // UTF-8 (for special characters) + const charset = document.createElement("meta"); + charset.setAttribute("charset", "UTF-8"); + document.head.appendChild(charset); + + // viewport (for mobile) + const viewport = document.createElement("meta"); + viewport.setAttribute("name", "viewport"); + viewport.setAttribute("content", "width=device-width, initial-scale=1.0"); + document.head.appendChild(viewport); + + // css + const proseCss = document.createElement("link"); + proseCss.rel = "stylesheet"; + proseCss.href = "prose.css"; + document.head.appendChild(proseCss); +} + function addHeader() { const header = document.querySelector("header"); - - const h1 = document.createElement('h1'); + + // Logo + const logoLink = document.createElement("a"); + logoLink.href = "/writing.html"; + logoLink.classList.add("header-logo-link"); + header.appendChild(logoLink); + + // Right-side content + const info = document.createElement("div"); + info.classList.add("header-info"); + + const h1 = document.createElement("h1"); h1.textContent = document.title; - header.appendChild(h1); - - const time = document.createElement('time'); + info.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' + 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'); + time.setAttribute("datetime", dateString); + time.textContent = `Published ${displayDate}`; + info.appendChild(time); + + header.appendChild(info); + + const hr = document.createElement("hr"); header.appendChild(hr); } @@ -47,6 +86,7 @@ function addTOC() { } document.addEventListener("DOMContentLoaded", () => { + addMetadata() addHeader() addTOC() })
\ No newline at end of file @@ -1,55 +1,55 @@ <!DOCTYPE html> <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"> - <script src="article.js"></script> -</head> -<body> - <article> - <header></header> + <head> + <title>I accidentally derived a formula for the Fibonacci Sequence</title> + <meta property="article:published_time" content="2026-06-11"> + <script src="article.js"></script> + </head> + <body> + <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> - <p>There is a flight of <math><mi>n</mi></math> stairs in front of you.</p> - <p>You can only ascend in steps of 1 or 2 stairs.</p> - <p>How many different ways are there for you to go up the stairs?</p> - </blockquote> - <p>For the rest of the article, I will go over my thought process (and stupidity) when solving the problem. Enjoy!</p> - <hr> - <p>First, I drew out some stairs and some tables to get base-cases and a feel for the problem.</p> - <table> - <thead> - <tr> - <th><strong># of stairs</strong></th> - <th><strong># of ways to ascend</strong></th> - </tr> - </thead> - <tbody> - <tr><td>1</td><td>1</td></tr> - <tr><td>2</td><td>2</td></tr> - <tr><td>3</td><td>3</td></tr> - <tr><td>4</td><td>5</td></tr> - <tr><td>5</td><td>8</td></tr> - </tbody> - </table> + <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> + <p>There is a flight of <math><mi>n</mi></math> + stairs in front of you.</p> + <p>You can only ascend in steps of 1 or 2 stairs.</p> + <p>How many different ways are there for you to go up the stairs?</p> + </blockquote> + <p>For the rest of the article, I will go over my thought process (and stupidity) when solving the problem. Enjoy!</p> + <hr> + <p>First, I drew out some stairs and some tables to get base-cases and a feel for the problem.</p> + <table> + <thead> + <tr> + <th><strong># of stairs</strong></th> + <th><strong># of ways to ascend</strong></th> + </tr> + </thead> + <tbody> + <tr><td>1</td><td>1</td></tr> + <tr><td>2</td><td>2</td></tr> + <tr><td>3</td><td>3</td></tr> + <tr><td>4</td><td>5</td></tr> + <tr><td>5</td><td>8</td></tr> + </tbody> + </table> - <p>Now, if you already see a familiar pattern in these numbers, shh! I somehow failed to see it, although it's obvious in hindsight, but I guess that's a good thing since it led me to the results of this article.</p> - <p>Next, I decided to take a look at the lengths of jumps. I made a list of numbers, where each number represented a jump of that height. So, for example, one ascent of the 10-stair staircase example might look like:</p> - <pre><code>[1 2 2 1 2 1 1]</code></pre> - <p>Since this list encodes a sequence of jumps that would climb 10 stairs, it's trivial to say the list must sum to 10.</p> - <p>This allowed me to rewrite the problem as the following:</p> - <blockquote> - <p>For any <math><mi>n</mi></math>, how many unique sequences of 2s and 1s add to <math><mi>n</mi></math>?</p> - </blockquote> - <p>While this was definitely more abstract, it reframed the problem using numbers, which I could play with using math.</p> - <hr> + <p>Now, if you already see a familiar pattern in these numbers, shh! I somehow failed to see it, although it's obvious in hindsight, but I guess that's a good thing since it led me to the results of this article.</p> + <p>Next, I decided to take a look at the lengths of jumps. I made a list of numbers, where each number represented a jump of that height. So, for example, one ascent of the 10-stair staircase example might look like:</p> + <pre><code>[1 2 2 1 2 1 1]</code></pre> + <p>Since this list encodes a sequence of jumps that would climb 10 stairs, it's trivial to say the list must sum to 10.</p> + <p>This allowed me to rewrite the problem as the following:</p> + <blockquote> + <p>For any <math><mi>n</mi></math>, how many unique sequences of 2s and 1s add to <math><mi>n</mi></math>?</p> + </blockquote> + <p>While this was definitely more abstract, it reframed the problem using numbers, which I could play with using math.</p> + <hr> - <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> - <pre><code>[1 1 2 1] + <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> + <pre><code>[1 1 2 1] [2 1 2] [1 2 1 1] [1 1 1 1 1] @@ -57,247 +57,262 @@ [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> - <tr> - <th scope="row"># of 2s (<math><mi>t</mi></math>)</th> - <td>0</td> - <td>1</td> - <td>2</td> - <td>3</td> - </tr> - <tr> - <th scope="row"></th> - <td><code>[1 1 1 1 1]</code></td> - <td><code>[2 1 1 1]<br>[1 2 1 1]<br>[1 1 2 1]<br>[1 1 1 2]</code></td> - <td><code>[1 2 2]<br>[2 1 2]<br>[2 2 1]</code></td> - <td></td> - </tr> - <tr> - <th scope="row">length of sequences (<math><msub><mi>k</mi><mi>t</mi></msub></math>)</th> - <td>5</td> - <td>4</td> - <td>3</td> - </tr> - <tr> - <th scope="row"># of sequences (<math><msub><mi>m</mi><mi>t</mi></msub></math>)</th> - <td>1</td> - <td>4</td> - <td>3</td> - <td>0</td> - </tr> - </tbody> - </table> - <p>This adds to a grand total of 8 sequences for a 5-stair staircase.</p> + <p>Grouping each sequence by the number of 2s in it yields the table:</p> + <table> + <tbody> + <tr> + <th scope="row"># of 2s (<math><mi>t</mi></math>)</th> + <td>0</td> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <th scope="row">sequences</th> + <td><code>[1 1 1 1 1]</code></td> + <td><code>[2 1 1 1]<br>[1 2 1 1]<br>[1 1 2 1]<br>[1 1 1 2]</code></td> + <td><code>[1 2 2]<br>[2 1 2]<br>[2 2 1]</code></td> + <td></td> + </tr> + <tr> + <th scope="row">length of sequences (<math><msub><mi>k</mi><mi>t</mi></msub></math>)</th> + <td>5</td> + <td>4</td> + <td>3</td> + </tr> + <tr> + <th scope="row"># of sequences (<math><msub><mi>m</mi><mi>t</mi></msub></math>)</th> + <td>1</td> + <td>4</td> + <td>3</td> + <td>0</td> + </tr> + </tbody> + </table> + <p>This adds to a grand total of 8 sequences for a 5-stair staircase.</p> - <p>From the table, I made a few insights:</p> - <ul> - <li><p> - Any two sequences with the same number of 2s have the same length. - This made sense because sequences with the same number of 2s always have the same number of 1s, so their total length must be the same. - </p></li> - <li><p> - <math><mi>k</mi></math> seemed to follow the pattern <math><mi>k</mi><mo>=</mo><mn>n</mn><mo>-</mo><mn>t</mn></math>. - This made sense because with each introduction of one more 2, then length of the list must decrease by 1 to still sum to 5. - </p></li> - <li><p> - The pattern <math><mi>k</mi><mo>=</mo><mn>n</mn><mo>-</mo><mn>t</mn></math> only worked for <math><mn>2</mn><mi>t</mi><mo>≤</mo><mi>n</mi></math>. - This made sense because if the sequence must sum to 5, then it can not contain 3 2s. This allowed me to define a general maximum for <math><mi>t</mi></math>. - <math display="block"> - <mrow> - <msub> - <mi>t</mi> - <mi>max</mi> - </msub> - <mo>=</mo> - <mo>⌊</mo> - <mfrac> - <mi>n</mi> - <mn>2</mn> - </mfrac> - <mo>⌋</mo> - </mrow> - </math> - </p></li> - </ul> - <hr> + <p>From the table, I made a few insights:</p> + <ul> + <li><p> + Any two sequences with the same number of 2s have the same length. + This made sense because sequences with the same number of 2s always have the same number of 1s, so their total length must be the same. + </p></li> + <li><p> + <math><mi>k</mi></math> + seemed to follow the pattern <math><mi>k</mi><mo>=</mo><mn>n</mn><mo>-</mo><mn>t</mn></math>. + This made sense because with each introduction of one more 2, then length of the list must decrease by 1 to still sum to 5. + </p></li> + <li><p> + The pattern <math><mi>k</mi><mo>=</mo><mn>n</mn><mo>-</mo><mn>t</mn></math> + only worked for <math><mn>2</mn><mi>t</mi><mo>≤</mo><mi>n</mi></math>. + This made sense because if the sequence must sum to 5, then it can not contain 3 2s. This allowed me to define a general maximum for <math><mi>t</mi></math>. + <math display="block"> + <mrow> + <msub> + <mi>t</mi> + <mi>max</mi> + </msub> + <mo>=</mo> + <mo>⌊</mo> + <mfrac> + <mi>n</mi> + <mn>2</mn> + </mfrac> + <mo>⌋</mo> + </mrow> + </math> + </p></li> + </ul> + <hr> - <p>Okay, so now that I had a way to split the sequences into pieces, I just needed a way to count the number of sequences for each value of <math><mi>t</mi></math>, and sum those together.</p> - <p>The sum is the easy part. I knew the final number would be the the sum of all of the <math><msub><mi>m</mi><mi>t</mi></msub></math>s, or:</p> - <math display="block"> - <mrow> - <munderover> - <mo>∑</mo> - <mrow> - <mi>t</mi> - <mo>=</mo> - <mn>0</mn> - </mrow> - <msub> - <mi>t</mi> - <mi>max</mi> - </msub> - </munderover> - <msub><mi>m</mi><mi>t</mi></msub> - </mrow> - </math> - <p>Plugging in our definition for <math><msub><mi>t</mi><mi>max</mi></msub></math>,</p> - <math display="block"> - <mrow> - <munderover> - <mo>∑</mo> - <mrow> - <mi>t</mi> - <mo>=</mo> - <mn>0</mn> - </mrow> - <mrow> - <mo>⌊</mo> - <mfrac> - <mi>n</mi> - <mn>2</mn> - </mfrac> - <mo>⌋</mo> - </mrow> - </munderover> - <msub><mi>m</mi><mi>t</mi></msub> - </mrow> - </math> + <p>Okay, so now that I had a way to split the sequences into pieces, I just needed a way to count the number of sequences for each value of <math><mi>t</mi></math>, and sum those together.</p> + <p>The sum was the easy part. I knew the final number would be the the sum of all of the <math><msub><mi>m</mi><mi>t</mi></msub></math>s, or:</p> + <math display="block"> + <mrow> + <munderover> + <mo>∑</mo> + <mrow> + <mi>t</mi> + <mo>=</mo> + <mn>0</mn> + </mrow> + <msub> + <mi>t</mi> + <mi>max</mi> + </msub> + </munderover> + <msub><mi>m</mi><mi>t</mi></msub> + </mrow> + </math> + <p>Plugging in our definition for <math><msub><mi>t</mi><mi>max</mi></msub></math>,</p> + <math display="block"> + <mrow> + <munderover> + <mo>∑</mo> + <mrow> + <mi>t</mi> + <mo>=</mo> + <mn>0</mn> + </mrow> + <mrow> + <mo>⌊</mo> + <mfrac> + <mi>n</mi> + <mn>2</mn> + </mfrac> + <mo>⌋</mo> + </mrow> + </munderover> + <msub><mi>m</mi><mi>t</mi></msub> + </mrow> + </math> - <p>After thinking about the sequences for a while, I realized that the number of sequences boils down to the question "how many ways can you uniquely arrange a list of <math><mi>t</mi></math> special items out of <math><mi>k</mi></math> total items?"</p> - <p>Then I realized: this was a combinatorics problem! Specifically, I remembered the Binomial Coefficient, or "<math><mi>n</mi></math> choose <math><mi>k</mi></math>", which counts "the number of ways to choose a subset of k elements from a larger set of n elements."</p> - <p>Each sequence of length <math><mi>k</mi></math> contained exactly <math><mi>t</mi></math> 2s. So counting the number of sequences <math><msub><mi>m</mi><mi>t</mi></msub></math> for some <math><mi>t</mi></math> was equivelant to counting the number of ways to choose <math><mi>t</mi></math> elements from a larger set of <math><mi>k</mi></math> elements. By this definition,</p> - <math display="block"> - <mrow> - <msub><mi>m</mi><mi>t</mi></msub> - <mo>=</mo> - <mo>(</mo> - <mfrac linethickness="0"> - <mrow> - <mi>k</mi> - </mrow> - <mi>t</mi> - </mfrac> - <mo>)</mo> - </mrow> - </math> - <p>Plugging in the prior definition for <math><mi>k</mi></math>, and using the summation from above, I arrived at a final equation:</p> - <math display="block"> - <mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo> - <mo>=</mo> - <mrow> - <munderover> - <mo>∑</mo> - <mrow> - <mi>t</mi> - <mo>=</mo> - <mn>0</mn> - </mrow> - <mrow> - <mo>⌊</mo> - <mfrac> - <mi>n</mi> - <mn>2</mn> - </mfrac> - <mo>⌋</mo> - </mrow> - </munderover> + <p>After thinking about the sequences for a while, I realized that the number of sequences boils down to the question "how many ways can you uniquely arrange a list of <math><mi>t</mi></math> + special items out of <math><mi>k</mi></math> + total items?"</p> + <p>Then I realized: this was a combinatorics problem! Specifically, I remembered the Binomial Coefficient, or "<math><mi>n</mi></math> + choose <math><mi>k</mi></math>", which counts "the number of ways to choose a subset of k elements from a larger set of n elements."</p> + <p>Each sequence of length <math><mi>k</mi></math> + contained exactly <math><mi>t</mi></math> + 2s. So counting the number of sequences <math><msub><mi>m</mi><mi>t</mi></msub></math> + for some <math><mi>t</mi></math> + was equivelant to counting the number of ways to choose <math><mi>t</mi></math> + elements from a larger set of <math><mi>k</mi></math> + elements. By this definition,</p> + <math display="block"> + <mrow> + <msub><mi>m</mi><mi>t</mi></msub> + <mo>=</mo> + <mo>(</mo> + <mfrac linethickness="0"> + <mrow> + <mi>k</mi> + </mrow> + <mi>t</mi> + </mfrac> + <mo>)</mo> + </mrow> + </math> + <p>Plugging in the prior definition for <math><mi>k</mi></math>, and using the summation from above, I arrived at a final equation:</p> + <math display="block"> + <mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo> + <mo>=</mo> + <mrow> + <munderover> + <mo>∑</mo> + <mrow> + <mi>t</mi> + <mo>=</mo> + <mn>0</mn> + </mrow> + <mrow> + <mo>⌊</mo> + <mfrac> + <mi>n</mi> + <mn>2</mn> + </mfrac> + <mo>⌋</mo> + </mrow> + </munderover> - <mrow> - <mo>(</mo> - <mfrac linethickness="0"> - <mrow> - <mi>n</mi> - <mo>-</mo> - <mi>t</mi> - </mrow> - <mi>t</mi> - </mfrac> - <mo>)</mo> - </mrow> - </mrow> - </math> - <p>Yay!</p> - <hr> + <mrow> + <mo>(</mo> + <mfrac linethickness="0"> + <mrow> + <mi>n</mi> + <mo>-</mo> + <mi>t</mi> + </mrow> + <mi>t</mi> + </mfrac> + <mo>)</mo> + </mrow> + </mrow> + </math> + <p>Yay!</p> + <hr> - <p>Okay, if you didn't see the pattern from the beginning, <code>[1 2 3 5 8]</code> is the <strong>Fibonacci Sequence</strong>, starting at the 2nd element. You might be more familiar with it starting from the 1st element <code>[1 1 2 3 5 8 ...]</code>.</p> - <p>So we really just derived a formula for the <math><mi>n</mi><mo>+</mo><mn>1</mn></math>th Fibonacci number!</p> - <p>When I first learned this, I thought I was some kind of genius or something, but a quick check on ProofWiki told that <a href="https://proofwiki.org/wiki/Fibonacci_Number_as_Sum_of_Binomial_Coefficients">a very similar function was already discovered in 1971</a>, so I guess I'm 55 years late to the party.</p> - <p>The formula on ProofWiki is for the <math><mi>n</mi></math>th fibonacci number, so here's how you could arrive at the real Fibonacci Number as Sum of Binomial Coefficients function:</p> - <p>Begin with the derived formula, replacing <math><mi>n</mi></math> for <math><mi>a</mi></math> to avoid variable conflicts later.</p> - <math display="block"> - <mi>f</mi><mo>(</mo><mi>a</mi><mo>)</mo> - <mo>=</mo> - <mrow> - <munderover> - <mo>∑</mo> - <mrow><mi>t</mi><mo>=</mo><mn>0</mn></mrow> - <mrow> - <mo>⌊</mo> - <mfrac><mi>a</mi><mn>2</mn></mfrac> - <mo>⌋</mo> - </mrow> - </munderover> - <mrow> - <mo>(</mo> - <mfrac linethickness="0"> - <mrow><mi>a</mi><mo>-</mo><mi>t</mi></mrow> - <mi>t</mi> - </mfrac> - <mo>)</mo> - </mrow> - </mrow> - </math> - <p>Since this is really for the <math><mi>n</mi><mo>+</mo><mn>1</mn></math>th Fibonacci number, substitute <math><mi>a</mi><mo>=</mo><mi>n</mi><mo>-</mo><mn>1</mn></math>.</p> - <math display="block"> - <mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo> - <mo>=</mo> - <mrow> - <munderover> - <mo>∑</mo> - <mrow><mi>t</mi><mo>=</mo><mn>0</mn></mrow> - <mrow> - <mo>⌊</mo> - <mfrac><mrow><mi>n</mi><mo>-</mo><mn>1</mn></mrow><mn>2</mn></mfrac> - <mo>⌋</mo> - </mrow> - </munderover> - <mrow> - <mo>(</mo> - <mfrac linethickness="0"> - <mrow><mi>n</mi><mo>-</mo><mi>t</mi><mo>-</mo><mn>1</mn></mrow> - <mi>t</mi> - </mfrac> - <mo>)</mo> - </mrow> - </mrow> - </math> - <p>The original function uses <math><mi>k</mi></math> instead of <math><mi>t</mi></math>.</p> - <math display="block"> - <mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo> - <mo>=</mo> - <mrow> - <munderover> - <mo>∑</mo> - <mrow><mi>k</mi><mo>=</mo><mn>0</mn></mrow> - <mrow> - <mo>⌊</mo> - <mfrac><mrow><mi>n</mi><mo>-</mo><mn>1</mn></mrow><mn>2</mn></mfrac> - <mo>⌋</mo> - </mrow> - </munderover> - <mrow> - <mo>(</mo> - <mfrac linethickness="0"> - <mrow><mi>n</mi><mo>-</mo><mi>k</mi><mo>-</mo><mn>1</mn></mrow> - <mi>k</mi> - </mfrac> - <mo>)</mo> - </mrow> - </mrow> - </math> - <p>This is the exact same function that appears on ProofWiki!</p> - </article> -</body> -</html>
\ No newline at end of file + <p>Okay, if you didn't see the pattern from the beginning, <code>[1 2 3 5 8]</code> + is the <strong>Fibonacci Sequence</strong>, starting at the 2nd element. You might be more familiar with it starting from the 1st element <code>[1 1 2 3 5 8 ...]</code>.</p> + <p>So we really just derived a formula for the <math><mi>n</mi><mo>+</mo><mn>1</mn></math>th Fibonacci number!</p> + <p>When I first learned this, I thought I was some kind of genius or something, but a quick check on ProofWiki told that <a href="https://proofwiki.org/wiki/Fibonacci_Number_as_Sum_of_Binomial_Coefficients">a very similar function was already discovered in 1971</a>, so I guess I'm 55 years late to the party.</p> + <p>The formula on ProofWiki is for the <math><mi>n</mi></math>th fibonacci number, so here's how you could arrive at the real Fibonacci Number as Sum of Binomial Coefficients function:</p> + <p>Begin with the derived formula, replacing <math><mi>n</mi></math> + for <math><mi>a</mi></math> + to avoid variable conflicts later.</p> + <math display="block"> + <mi>f</mi><mo>(</mo><mi>a</mi><mo>)</mo> + <mo>=</mo> + <mrow> + <munderover> + <mo>∑</mo> + <mrow><mi>t</mi><mo>=</mo><mn>0</mn></mrow> + <mrow> + <mo>⌊</mo> + <mfrac><mi>a</mi><mn>2</mn></mfrac> + <mo>⌋</mo> + </mrow> + </munderover> + <mrow> + <mo>(</mo> + <mfrac linethickness="0"> + <mrow><mi>a</mi><mo>-</mo><mi>t</mi></mrow> + <mi>t</mi> + </mfrac> + <mo>)</mo> + </mrow> + </mrow> + </math> + <p>Since this is really for the <math><mi>n</mi><mo>+</mo><mn>1</mn></math>th Fibonacci number, substitute <math><mi>a</mi><mo>=</mo><mi>n</mi><mo>-</mo><mn>1</mn></math>.</p> + <math display="block"> + <mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo> + <mo>=</mo> + <mrow> + <munderover> + <mo>∑</mo> + <mrow><mi>t</mi><mo>=</mo><mn>0</mn></mrow> + <mrow> + <mo>⌊</mo> + <mfrac><mrow><mi>n</mi><mo>-</mo><mn>1</mn></mrow><mn>2</mn></mfrac> + <mo>⌋</mo> + </mrow> + </munderover> + <mrow> + <mo>(</mo> + <mfrac linethickness="0"> + <mrow><mi>n</mi><mo>-</mo><mi>t</mi><mo>-</mo><mn>1</mn></mrow> + <mi>t</mi> + </mfrac> + <mo>)</mo> + </mrow> + </mrow> + </math> + <p>The original function uses <math><mi>k</mi></math> + instead of <math><mi>t</mi></math>.</p> + <math display="block"> + <mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo> + <mo>=</mo> + <mrow> + <munderover> + <mo>∑</mo> + <mrow><mi>k</mi><mo>=</mo><mn>0</mn></mrow> + <mrow> + <mo>⌊</mo> + <mfrac><mrow><mi>n</mi><mo>-</mo><mn>1</mn></mrow><mn>2</mn></mfrac> + <mo>⌋</mo> + </mrow> + </munderover> + <mrow> + <mo>(</mo> + <mfrac linethickness="0"> + <mrow><mi>n</mi><mo>-</mo><mi>k</mi><mo>-</mo><mn>1</mn></mrow> + <mi>k</mi> + </mfrac> + <mo>)</mo> + </mrow> + </mrow> + </math> + <p>This is the exact same function that appears on ProofWiki!</p> + </article> + </body> +</html> diff --git a/fonts.css b/fonts.css new file mode 100644 index 0000000..7ce76d4 --- /dev/null +++ b/fonts.css @@ -0,0 +1,37 @@ +@font-face { + font-family: "Gelasio"; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url("/fonts/gelasio-latin.woff2") format("woff2"); +} + +@font-face { + font-family: "Gelasio"; + font-style: italic; + font-weight: 400 700; + font-display: swap; + src: url("/fonts/gelasio-latin-italic.woff2") format("woff2"); +} + +@font-face { + font-family: "Fira Math"; + src: url("/fonts/FiraMath-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Fira Code"; + src: url("/fonts/FiraCode-VF.woff2") format("woff2"); + font-style: normal; + font-weight: 300 700; + font-display: swap; +} + +:root { + --fontSerif: 'Gelasio', serif; + --fontMono: 'Fira Code', monospace; + --fontMath: 'Fira Math', serif; +}
\ No newline at end of file diff --git a/fonts/FiraCode-VF.woff2 b/fonts/FiraCode-VF.woff2 Binary files differnew file mode 100644 index 0000000..e755a9d --- /dev/null +++ b/fonts/FiraCode-VF.woff2 diff --git a/fonts/FiraMath-Regular.woff2 b/fonts/FiraMath-Regular.woff2 Binary files differnew file mode 100644 index 0000000..fd08e65 --- /dev/null +++ b/fonts/FiraMath-Regular.woff2 diff --git a/fonts/gelasio-latin-italic.woff2 b/fonts/gelasio-latin-italic.woff2 Binary files differnew file mode 100644 index 0000000..7f95e1d --- /dev/null +++ b/fonts/gelasio-latin-italic.woff2 diff --git a/fonts/gelasio-latin.woff2 b/fonts/gelasio-latin.woff2 Binary files differnew file mode 100644 index 0000000..fe18998 --- /dev/null +++ b/fonts/gelasio-latin.woff2 diff --git a/forgejno.html b/forgejno.html index e36b358..100c606 100644 --- a/forgejno.html +++ b/forgejno.html @@ -3,7 +3,6 @@ <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> diff --git a/minimalnetworkinglaptop.html b/minimalnetworkinglaptop.html index f9ca405..bc07c9b 100644 --- a/minimalnetworkinglaptop.html +++ b/minimalnetworkinglaptop.html @@ -1,52 +1,49 @@ <!DOCTYPE html> <html> -<head> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <head> + <title>Minimal Laptop Networking with NixOS</title> + <meta property="article:published_time" content="2026-07-29"> + <script src="article.js"></script> + </head> + <body> + <article> + <header></header> - <title>Minimal Laptop Networking with NixOS</title> - <link rel="stylesheet" href="prose.css"> - <meta property="article:published_time" content="2026-07-29"> - <script src="article.js"></script> -</head> -<body> - <article> - <header></header> + <p> + The default networking stack on NixOS uses NetworkManager. + While it's a great choice for many users, I prefer a lighter setup built around systemd-networkd, systemd-resolved, and iwd. It integrates well with the rest of the systemd stack, has fewer moving pieces, and exposes powerful networking features without an additional network management daemon. + </p> - <p> - The default networking stack on NixOS uses NetworkManager. - While it's a great choice for many users, I prefer a lighter setup built around systemd-networkd, systemd-resolved, and iwd. It integrates well with the rest of the systemd stack, has fewer moving pieces, and exposes powerful networking features without an additional network management daemon. - </p> + <p class="comment"> + This article focuses on laptops that connect to wifi. + If you're configuring a server, you probably don't need iwd at all; systemd-networkd and systemd-resolved are usually sufficient. + </p> - <p class="comment"> - This article focuses on laptops that connect to wifi. - If you're configuring a server, you probably don't need iwd at all; systemd-networkd and systemd-resolved are usually sufficient. - </p> + <div id="toc"></div> - <div id="toc"></div> + <h2>The default stack</h2> + <p>If not configured otherwise, NixOS uses the following stack:</p> + <ul> + <li>NetworkManager for wifi</li> + <li>dhcpcd for DHCP</li> + <li>resolvconf for DNS</li> + <li>a "legacy script based system" for network configuration<sup><a href="https://search.nixos.org/options?channel=26.05&query=useNetworkd&type=options#show=option%253Anetworking.useNetworkd:~:text=legacy%20script%20based%20system">[1]</a></sup></li> + </ul> + <p>We'll be replacing it with:</p> + <ul> + <li>iwd for wifi</li> + <li>systemd-networkd for DHCP and network configuration</li> + <li>systemd-resolved for DNS</li> + </ul> + <p> + This has a few benefits. + Firstly, switching to resolved allows you to enable features like encrypted DNS over TLS (DoT) and DNSSEC validation, which reduces the potential for DNS spoofing and man-in-the-middle attacks<sup><a href="https://whynothugo.nl/journal/2026/06/24/ignore-dnssec-if-you-like-mitm-attacks/#distributions-and-oss">[2]</a></sup>. + Additionally, the default stack uses 3 extra packages (NM, dhcpcd, resolvconf) and some bash scripts, while this setup only uses one extra package (iwd). + Also, I'm kind of a systemd nerd, and this is a way to dig into systemd a bit more. + </p> - <h2>The default stack</h2> - <p>If not configured otherwise, NixOS uses the following stack:</p> - <ul> - <li>NetworkManager for wifi</li> - <li>dhcpcd for DHCP</li> - <li>resolvconf for DNS</li> - <li>a "legacy script based system" for network configuration<sup><a href="https://search.nixos.org/options?channel=26.05&query=useNetworkd&type=options#show=option%253Anetworking.useNetworkd:~:text=legacy%20script%20based%20system">[1]</a></sup></li> - </ul> - <p>We'll be replacing it with:</p> - <ul> - <li>iwd for wifi</li> - <li>systemd-networkd for DHCP and network configuration</li> - <li>systemd-resolved for DNS</li> - </ul> - <p> - This has a few benefits. - Firstly, switching to resolved allows you to enable features like encrypted DNS over TLS (DoT) and DNSSEC validation, which reduces the potential for DNS spoofing and man-in-the-middle attacks<sup><a href="https://whynothugo.nl/journal/2026/06/24/ignore-dnssec-if-you-like-mitm-attacks/#distributions-and-oss">[2]</a></sup>. - Additionally, the default stack uses 3 extra packages (NM, dhcpcd, resolvconf) and some bash scripts, while this setup only uses one extra package (iwd). - Also, I'm kind of a systemd nerd, and this is a way to dig into systemd a bit more. - </p> - - <p>So the first thing we can do is disable all of the default stuff:</p> - <pre><code>{ + <p>So the first thing we can do is disable all of the default stuff:</p> + <pre><code>{ <prop>networking</prop> = { <prop>networkmanager</prop>.<prop>enable</prop> = <kw>false</kw>; <prop>dhcpcd</prop>.<prop>enable</prop> = <kw>false</kw>; @@ -55,14 +52,14 @@ }; }</code></pre> - <p>And let's get to configuring!</p> + <p>And let's get to configuring!</p> - <h2>iwd</h2> - <p> - First up is iwd. - The important parts of the configuration are delegating network configuration to networkd, delegating name resolution (DNS) to resolved, and configuring MAC randomization for some extra privacy. - </p> - <pre><code>{ + <h2>iwd</h2> + <p> + First up is iwd. + The important parts of the configuration are delegating network configuration to networkd, delegating name resolution (DNS) to resolved, and configuring MAC randomization for some extra privacy. + </p> + <pre><code>{ <prop>networking</prop>.<prop>wireless</prop>.<prop>iwd</prop> = { <prop>enable</prop> = <kw>true</kw>; <prop>settings</prop> = { @@ -76,28 +73,28 @@ }; }</code></pre> - <p> - At this point, we don't have any network configuration yet, so internet access won't work. However, you can verify that iwd is functioning correctly: - </p> - <ul> - <li><code>iwctl station wlan0 scan</code>: Scans for new wifi networks</li> - <li><code>iwctl station wlan0 get-networks</code>: lists the networks that it found</li> - <li><code>iwctl station wlan0 connect "<SSID>"</code>: connects to the network, prompting for a PSK if necessary</li> - </ul> + <p> + At this point, we don't have any network configuration yet, so internet access won't work. However, you can verify that iwd is functioning correctly: + </p> + <ul> + <li><code><kw>iwctl</kw> <prop>station</prop> <prop>wlan0</prop> <prop>scan</prop></code>: Scans for new wifi networks</li> + <li><code><kw>iwctl</kw> <prop>station</prop> <prop>wlan0</prop> <prop>get-networks</prop></code>: lists the networks that it found</li> + <li><code><kw>iwctl</kw> <prop>station</prop> <prop>wlan0</prop> <prop>connect</prop> <str>"<SSID>"</str></code>: connects to the network, prompting for a PSK if necessary</li> + </ul> - <h2>systemd-networkd</h2> - <p> - Next up is systemd-networkd. - Networkd has lots of advanced options for things like static IP config, Wireguard tunnels, VLANs, domain-based DNS routing, and more. - However, here we'll just do a basic configuration to enable DHCP and deny DHCP-advertised DNS. - </p> - <details class="comment"> - <summary>why disable DHCP-advertised DNS?</summary> - By default, most networks advertise their own DNS resolver via DHCP. - Since we'll configure our own encrypted upstream DNS servers in the next section, we tell networkd to ignore the ones provided by the network. - </details> + <h2>systemd-networkd</h2> + <p> + Next up is systemd-networkd. + Networkd has lots of advanced options for things like static IP config, Wireguard tunnels, VLANs, domain-based DNS routing, and more. + However, here we'll just do a basic configuration to enable DHCP and deny DHCP-advertised DNS. + </p> + <details class="comment"> + <summary>why disable DHCP-advertised DNS?</summary> + By default, most networks advertise their own DNS resolver via DHCP. + Since we'll configure our own encrypted upstream DNS servers in the next section, we tell networkd to ignore the ones provided by the network. + </details> - <pre><code>{ + <pre><code>{ <prop>systemd</prop>.<prop>network</prop> = { <prop>enable</prop> = <kw>true</kw>; @@ -113,27 +110,28 @@ <prop>networking</prop>.<prop>useNetworkd</prop> = <kw>true</kw>; }</code></pre> - <p> - The <code>name = "wl*"</code> refers to the network interface name that this configuration applies to. - According to the systemd docs, wireless LAN devices should start with the two-character prefix "wl"<sup><a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.net-naming-scheme.html#:~:text=wl,WLAN">[3]</a></sup>. - Here we use a glob (<code>*</code>) to match all wireless interfaces. - </p> + <p> + The <code>name = "wl*"</code> + refers to the network interface name that this configuration applies to. + According to the systemd docs, wireless LAN devices should start with the two-character prefix "wl"<sup><a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.net-naming-scheme.html#:~:text=wl,WLAN">[3]</a></sup>. + Here we use a glob (<code>*</code>) to match all wireless interfaces. + </p> - <p>You can verify that your configuration worked with a few commands:</p> - <ul> - <li><code>ip addr</code>: make sure that there's an IP address associated with your wireless interface. That means DHCP is working.</li> - <li><code>networkctl status</code>: make sure there's no significant errors in the logs.</li> - </ul> + <p>You can verify that your configuration worked with a few commands:</p> + <ul> + <li><code><kw>ip</kw> <prop>addr</prop></code>: make sure that there's an IP address associated with your wireless interface. That means DHCP is working.</li> + <li><code><kw>networkctl</kw> <prop>status</prop></code>: make sure there's no significant errors in the logs.</li> + </ul> - <h2>systemd-resolved</h2> - <p> - Now all we have left is DNS configuration, then we're done. - This is probably the part where you can nerd out the hardest, but I try to keep it pretty minimal. - The important parts are selecting upstream DNS servers that support secure DNS protocols (like DNS over TLS and DNSSEC) and that respect your privacy. - For these reasons, I use <a href="https://quad9.net">quad9</a>, but do your own research and find the best upstream DNS server for you! - Some other options include <a href="https://www.cloudflare.com/products/dns/">Cloudflare DNS</a>, <a href="https://developers.google.com/speed/public-dns/">Google's DNS</a>, or <a href="https://adguard-dns.io/en/public-dns.html">AdGuard Public DNS</a>. - </p> - <pre><code>{ + <h2>systemd-resolved</h2> + <p> + Now all we have left is DNS configuration, then we're done. + This is probably the part where you can nerd out the hardest, but I try to keep it pretty minimal. + The important parts are selecting upstream DNS servers that support secure DNS protocols (like DNS over TLS and DNSSEC) and that respect your privacy. + For these reasons, I use <a href="https://quad9.net">quad9</a>, but do your own research and find the best upstream DNS server for you! + Some other options include <a href="https://www.cloudflare.com/products/dns/">Cloudflare DNS</a>, <a href="https://developers.google.com/speed/public-dns/">Google's DNS</a>, or <a href="https://adguard-dns.io/en/public-dns.html">AdGuard Public DNS</a>. + </p> + <pre><code>{ <prop>networking</prop>.<prop>nameservers</prop> = [ <str>"9.9.9.9#dns.quad9.net"</str> <str>"149.112.112.112#dns.quad9.net"</str> @@ -151,23 +149,25 @@ }; }</code></pre> - <p>You can verify your DNS configuration by using the <code>resolvectl status</code> command or by just opening a website.</p> + <p>You can verify your DNS configuration by using the <code><kw>resolvectl</kw> <prop>status</prop></code> + command or by just opening a website.</p> - <h2>P.S. Captive Portals</h2> - <p> - Captive portals are those wifi login pages that pop up when you connect to some public wifi networks. - Some captive portals don't behave correctly when you're using an encrypted third-party DNS server instead of the network's advertised resolver. - To get around this, I use <a href="https://github.com/FiloSottile/captive-browser">Captive Browser</a> (<a href="https://search.nixos.org/packages?channel=26.05&query=captive-browser#show=captive-browser">nixpkgs</a>), which launches an instance of Chromium through a SOCKS5 proxy, avoiding the system DNS configuration altogether. - This allows you to access a captive portal for long enough to log in and gain internet access. - Pretty neat! - </p> + <h2>P.S. Captive portals</h2> + <p> + Captive portals are those wifi login pages that pop up when you connect to some public wifi networks. + Some captive portals don't behave correctly when you're using an encrypted third-party DNS server instead of the network's advertised resolver. + To get around this, I use <a href="https://github.com/FiloSottile/captive-browser">Captive Browser</a> + (<a href="https://search.nixos.org/packages?channel=26.05&query=captive-browser#show=captive-browser">nixpkgs</a>), which launches an instance of Chromium through a SOCKS5 proxy, avoiding the system DNS configuration altogether. + This allows you to access a captive portal for long enough to log in and gain internet access. + Pretty neat! + </p> - <h2>P.P.S. I miss nm-applet</h2> - <p> - Probably the biggest downside to this setup is the lack of desktop integration. - GNOME and Plasma only support NetworkManager in their network configuration menu, so you're kinda out of luck. - For tiling window manager people, I can recommend <a href="https://github.com/e-tho/iwmenu">iwmenu</a>, which is a launcher (dmenu, rofi, fuzzel)-based menu for connecting to wifi networks via iwd. - </p> - </article> -</body> -</html>
\ No newline at end of file + <h2>P.P.S. I miss nm-applet</h2> + <p> + Probably the biggest downside to this setup is the lack of desktop integration. + GNOME and Plasma only support NetworkManager in their network configuration menu, so you're kinda out of luck. + For tiling window manager people, I can recommend <a href="https://github.com/e-tho/iwmenu">iwmenu</a>, which is a launcher (dmenu, rofi, fuzzel)-based menu for connecting to wifi networks via iwd. + </p> + </article> + </body> +</html> @@ -2,7 +2,6 @@ <html> <head> <title>Taking a break</title> - <link rel="stylesheet" href="prose.css"> <script src="article.js"></script> </head> <body> @@ -1,6 +1,11 @@ -@import url('https://fonts.googleapis.com/css2?family=Gelasio:ital,wght@0,400..700;1,400..700&display=swap'); @import url('style.css'); +:root { + --sx: 1rem; + --lx: 3rem; + --lh: 1.5; +} + article { width: 100%; max-width: 75ch; @@ -11,13 +16,22 @@ article { p { color: #e6e6e6; - font-family: 'Gelasio'; - line-height: 1.3; + font-family: var(--fontSerif); + line-height: var(--lh); font-size: 1.1rem; + margin: var(--sx) 0; +} + +sup { + font-size: 0.8em; + line-height: 0; + vertical-align: baseline; + position: relative; + top: -0.5em; } a { - color: #4DA3FF; + color: #49f; text-decoration: none; &:hover { @@ -27,8 +41,8 @@ a { li { color: #e6e6e6; - font-family: 'Gelasio'; - line-height: 1.3; + font-family: var(--fontSerif); + line-height: var(--lh); font-size: 1.1rem; &::marker { @@ -36,29 +50,12 @@ li { } } -h1 { - color: #e6e6e6; - line-height: 1.15; - margin-bottom: 0; - font-family: 'Gelasio'; - font-weight: 600; -} - -h2 { - color: #e6e6e6; - line-height: 1; - margin-top: 3rem; - margin-bottom: 0; - font-family: 'Gelasio'; - font-weight: 600; -} - -h3 { +h1, h2, h3 { color: #e6e6e6; - line-height: 1; - margin-top: 2rem; - margin-bottom: 0; - font-family: 'Gelasio'; + margin-top: var(--lx); + margin-bottom: var(--sx); + font-family: var(--fontSerif); + line-height: var(--lh); font-weight: 600; } @@ -71,15 +68,14 @@ hr { height: 2px; border: 0; border-top: 2px solid #888; - margin: 1em 0; + margin: var(--sx) 0; padding: 0; } time { display: block; - font-family: 'Gelasio'; + font-family: var(--fontSerif); color: #888; - margin-top: 0.5rem; } @media (max-width: 1000px) { @@ -94,7 +90,7 @@ time { } details { - font-family: 'Gelasio'; + font-family: var(--fontSerif); color: #888; summary::marker { @@ -103,13 +99,18 @@ details { } table { + --tsx_x: calc(var(--sx) / 3); + --tsx_y: calc(var(--sx) / 1.5); + + font-family: var(--fontSerif); + margin: var(--sx) 0; + border-collapse: collapse; - margin: 0 auto; th { color: #e6e6e6; text-align: center; border: solid; - padding: 0 1.5ch; + padding: var(--tsx_x) var(--tsx_y); border-width: 0 0 1px 0; &[scope="row"] { @@ -117,40 +118,94 @@ table { } } td { - color: #00ff78; + color: #6e7; text-align: center; - padding: 0 1.5ch; + padding: var(--tsx_x) var(--tsx_y); } } -code .path { - color: #888; +math { + font-family: var(--fontMath); + font-size: 1.2rem; + transform: translateZ(0); + + msub > :nth-child(2) { + transform: translateY(-0.2em); + font-size: 0.8rem; + } } -/* inline code */ +/* CODEBLOCKS */ code { - color: #00ff78; - font-family: 'Roboto Mono'; + font-size: 0.95rem; + color: #e6e6e6; + white-space: pre; + font-family: 'Fira Code', monospace; + + kw { color: #49f; } + ty { color: #4d9; } + fn { color: #7bf; } + mac { color: #d69; } + str { color: #6e7; } + num { color: #9c9; } + com { color: #888; } + prop { color: #8cf; } + mod { color: #bcd; } + id { color: inherit; } } -/* CODEBLOCKS */ pre { - color: #00ff78; - font-family: 'Roboto Mono'; + max-width: 100%; + overflow-x: auto; + overflow-y: hidden; + box-sizing: border-box; - code { - color: #e6e6e6; + margin: var(--sx) 0; + line-height: var(--lh); +} + +/* HEADER */ +header { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + grid-template-rows: auto auto; + gap: var(--sx); + + .header-logo-link { + display: block; + height: 100%; + aspect-ratio: 1; + + background-color: #e6e6e6; + + mask-image: url("/Logo.svg"); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; } - 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 */ -} + .header-logo-link:hover { + background-color: #49f; + } + + .header-info { + min-width: 0; + align-self: center; + h1 { + line-height: 1; + margin: 0; + } + } + + time { + line-height: var(--lh); + margin: 0; + } + + hr { + margin: 0; + grid-column: 1 / -1; + width: 100%; + } +}
\ No newline at end of file diff --git a/reversible.html b/reversible.html index 378045f..24b5b87 100644 --- a/reversible.html +++ b/reversible.html @@ -2,11 +2,8 @@ <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> @@ -24,18 +21,21 @@ <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> + So remember in math class when you learned to take the inverse of a function? + For example, say you have <code>y = 3*x + 6</code>. + You can take the inverse by solving for <code>x</code>. + In this case, the inverse of our function is <code>x = y/3 - 2</code>. + 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>Database migrations are only <em>half</em> as scary (writing your <code><fn>up</fn></code> script automatically gives you your <code><fn>down</fn></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. + 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> @@ -58,7 +58,7 @@ <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. + We can model this with a struct and an impl of the <code><ty>Rev</ty></code> trait. </p> <pre><code><kw>struct</kw> <ty>Addition</ty><<ty>A</ty>> { <prop>amount</prop>: <ty>A</ty> @@ -84,7 +84,7 @@ <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. + <span class="comment">Honestly these are light work compared to what's coming up.</span> </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>> { @@ -95,7 +95,7 @@ <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>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> @@ -103,14 +103,14 @@ 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> + <li><code><fn>neg</fn>()</code> negates a number</li> + <li><code><fn>times</fn>(x)</code> multiplies a number by x</li> + <li><code><fn>discrete</fn>(x: <ty>Vec</ty><(<ty>A</ty>, <ty>B</ty>)>)</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>. + The first combinator we'll implement is <code><ty>Pipe</ty></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>); @@ -132,7 +132,7 @@ }</code></pre> <p> - Additionally, we'll implement <code>Inverse</code> (not to be confused with <code>.reverse()</code>). + Additionally, we'll implement <code><ty>Inverse</ty></code> (not to be confused with <code>.<fn>reverse</fn>()</code>). This flips the forward and reverse methods. </p> <pre><code><kw>struct</kw> <ty>Inverse</ty><<ty>X</ty>>(<ty>X</ty>); @@ -194,7 +194,7 @@ <h2 id="under">Under</h2> <p> - <code>under</code> is a higher order combinator, which means it's implemented in terms of other combinators. + <code><fn>under</fn></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> @@ -3,7 +3,6 @@ <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> @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap'); +@import url('fonts.css'); *, *::before, *::after { box-sizing: border-box; |
