<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>bytes.zone</title>
	<link href="https://bytes.zone/index.xml" rel="self" type="application/atom+xml"/>
  <link href="https://bytes.zone"/>
	<generator uri="https://www.getzola.org/">Zola</generator>
	<updated>2026-02-11T00:00:00+00:00</updated>
	<id>https://bytes.zone/index.xml</id>
	<entry xml:lang="en">
		<title>Winter Pinball League</title>
		<published>2026-02-11T00:00:00+00:00</published>
		<updated>2026-02-11T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/2026-winter-pinball-league/" type="text/html"/>
		<id>https://bytes.zone/projects/2026-winter-pinball-league/</id>
		<content type="html">&lt;p&gt;I joined a pinball league at a local barcade! I&#x27;ve got like… &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;stlouis.league.papa.org&#x2F;playerInfo&#x2F;1671&quot;&gt;lifetime stats&lt;&#x2F;a&gt; now!&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s been really fun so far, but, as you can see, I&#x27;m not that good yet. Pinball wizard? Nah, but maybe a pinball apprentice.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Software estimation that doesn&#x27;t suck</title>
		<published>2025-12-04T00:00:00+00:00</published>
		<updated>2025-12-04T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/software-estimation/" type="text/html"/>
		<id>https://bytes.zone/posts/software-estimation/</id>
		<content type="html">&lt;p&gt;You&#x27;ve been asked to give an estimate about when you&#x27;re gonna be able to ship some code. Fine fine, except that there&#x27;s something riding on this… a trade show, seasonal deadline, etc. Point is: there&#x27;s another part of the business depending on you doing a good job here.&lt;&#x2F;p&gt;
&lt;p&gt;But software estimates are notoriously inaccurate, to the degree that people frequently double or halve them depending on what they think of the team. Think about that for a sec: a change of &lt;strong&gt;50% in either direction based solely on personal factors.&lt;&#x2F;strong&gt; Absolutely wild.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Part of that is that software engineers have a weird aversion to figuring out how to do better here. As a result, a lot of people never get better than off-the-cuff &quot;I can knock that out in a week&quot; style estimates, and develop a reputation for being inaccurate. These skills are worth learning, though, if only because giving useful estimates means that you have the time you need to do the rest of your job correctly! You can also build credibility as someone who understands how to navigate complex systems, and end up with more of a say in the processes you&#x27;re a part of.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m going to share a process that I&#x27;ve personally used to prepare and update estimates for all sorts of software projects. It&#x27;s based based off the advice in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.microsoftpressstore.com&#x2F;store&#x2F;software-estimation-demystifying-the-black-art-9780735690851&quot;&gt;&lt;em&gt;Software Estimation: Demystifying the Black Art&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; by Steve McConnell. I&#x27;m not following this book to the letter, but the process ends up being enough to communicate with to some degree of accuracy. That&#x27;s fine, since that&#x27;s typically what you&#x27;re really after. However, it&#x27;s possible that I&#x27;ve fatally compromised some important statistical property. If you read this and you notice, I&#x27;d appreciate a heads-up!&lt;&#x2F;p&gt;
&lt;p&gt;This is going to be a longer post, but here&#x27;s the overall process in five steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Break down the work into high-level outcome-focused areas.&lt;&#x2F;li&gt;
&lt;li&gt;Break each item down into tasks small enough to feel familiar.&lt;&#x2F;li&gt;
&lt;li&gt;Go to your team and ask them to give a &quot;about this long&quot; guess as well as a best case and a worst-case for each item.&lt;&#x2F;li&gt;
&lt;li&gt;Do a little math to get a range based on percentage confidence.&lt;&#x2F;li&gt;
&lt;li&gt;Keep your estimates up to date as the project progresses.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This has worked well for me in companies which lean more towards big-plan-up-front as well as companies that try to stick with a more &quot;agile&quot; approach.&lt;&#x2F;p&gt;
&lt;p&gt;However, sometimes people read this and think &quot;oh no! It&#x27;s waterfall!&quot; and bounce off it without getting any benefit. So: there&#x27;s nothing about this process that prevents you from breaking the work into smaller independently-releasable components, or shrinking the project scope, or putting things behind feature flags for beta users, or whatever release strategy you&#x27;re comfortable with. If you&#x27;re in an organization that&#x27;s all-in on agile, you could even use this process to just make sure you&#x27;re not overcommitting in a single sprint.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also usually true that scope or project management style are things you&#x27;re inheriting instead of something you have direct influence over. Even when you do, working up a proper estimate builds long-term credibility that lets you have more influence over the system.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#cassandra&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;high-level-breakdown&quot;&gt;High-Level Breakdown&lt;&#x2F;h2&gt;
&lt;p&gt;To get our top level breakdown, we need to address two problems:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;People usually communicate estimates as single points, but ranges are more realistic.&lt;&#x2F;strong&gt; There are always a bunch of possible outcomes, and we need to be able to cover them. So instead of saying &quot;3 weeks&quot;, you might say &quot;we&#x27;re 90% confident that we&#x27;ll land it in 1–5 weeks.&quot; That is, given all our possible outcomes, 90% of them end up shipping in that time. If you &lt;em&gt;must&lt;&#x2F;em&gt; give a single point, give the upper end of the range.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;People are really bad at giving percentage-confident estimates to work up a range.&lt;&#x2F;strong&gt; That gets worse as the range expands: you could easily give me a 90% confident estimate for the weight of a glass of water, but if I ask you for the weight of all the water in Lake Michigan you&#x27;re likely to get it wrong by a couple orders of magnitude. (If you want to try, make a guess and then check the answer in the footnotes.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#lake-michigan&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We&#x27;ll have to overcome both issues to succeed. To start, we&#x27;re going to break down work into high level areas. Say we&#x27;re adding emoji reactions to posts on some businessware app. We might think of these as the high-level components:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Clicking the reaction button and selecting an emoji adds it to the post.&lt;&#x2F;li&gt;
&lt;li&gt;Clicking an existing reaction adds +1 to it along with your name. If you had already done that, it removes the +1 instead.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;From here, we&#x27;d break this down into some finer-grained work items:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Add reaction button to posts&lt;&#x2F;li&gt;
&lt;li&gt;Handle request to add reaction to post&lt;&#x2F;li&gt;
&lt;li&gt;Send reactions+attribution to the frontend along with post payload&lt;&#x2F;li&gt;
&lt;li&gt;Show people who reacted on hover&lt;&#x2F;li&gt;
&lt;li&gt;Add +1 to the reaction on click
&lt;ul&gt;
&lt;li&gt;Uses the same backend route as adding a new reaction, just a slightly different UX&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Remove your own reaction if clicked when present&lt;&#x2F;li&gt;
&lt;li&gt;Handle request to remove reaction from post&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You might get these by talking through the functionality with a designer or PM. Starting out high-level and user-focused usually helps to ensure you don&#x27;t miss anything.&lt;&#x2F;p&gt;
&lt;p&gt;In addition to breaking down the work, this is a good place to make sure that we&#x27;re not missing any implicit work that we need to do. Missing items are the biggest reason why my estimates have been inaccurate in the past, so making sure to consider all the work in the project—even the indirect or wrap-up stuff—helps a lot!&lt;&#x2F;p&gt;
&lt;p&gt;For example, does this work require a refactor? What&#x27;s new&#x2F;unusual about this? What happens in failure modes? I ask questions about the project using something I call &quot;the &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;feature-proofing-checklist&#x2F;&quot;&gt;feature-proofing checklist&lt;&#x2F;a&gt;.&quot; As I said, missing work has been the biggest source of inaccuracy when preparing estimates, so don&#x27;t skip it!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;task-level-breakdown&quot;&gt;Task-Level Breakdown&lt;&#x2F;h2&gt;
&lt;p&gt;Next we need to break these tasks down further to prepare them for estimation. You&#x27;ll know something is small enough if you could imagine it being in a single PR. If your team regularly uses huge PRs, break it down to items that would take less than two days instead.&lt;&#x2F;p&gt;
&lt;p&gt;You&#x27;d do this for all of the tasks, but for the sake of keeping things brief, I&#x27;m only going to do it for a couple:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Add reaction button to posts
&lt;ul&gt;
&lt;li&gt;Upgrade post component to our new standard pattern&lt;&#x2F;li&gt;
&lt;li&gt;Add reaction button, styles, event handlers with DOM tests&lt;&#x2F;li&gt;
&lt;li&gt;Test for keyboard and screen reader accessibility&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Handle request to add reaction to post
&lt;ul&gt;
&lt;li&gt;Add API endpoint to handle POST&lt;&#x2F;li&gt;
&lt;li&gt;Add database table to store many-to-many relationship between posts and users with reaction data&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;m spitballing here, since this isn&#x27;t a real app, but let&#x27;s pretend that the existing post component is pretty messy. Maybe it&#x27;s old code—one of the first things added in this project—and it hasn&#x27;t had any love in a while. This is the perfect time to upgrade it, since doing so will remove incidental complexity and make future updates faster.&lt;&#x2F;p&gt;
&lt;p&gt;The important thing here is to go from &quot;weight of Lake Michigan&quot; to &quot;weight of a glass of water&quot;: you want to get these into small enough chunks that the work gets familiar enough that people can imagine actually doing it. This is something you were probably going to have to do anyway to make the work legible to stakeholders; you might as well get the bonus of an accurate estimate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-big-is-each-task&quot;&gt;How Big is Each Task?&lt;&#x2F;h2&gt;
&lt;p&gt;Next, we&#x27;ll prepare time-based estimates. We need three numbers for each task in your breakdown. Specifically:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;normal-case&lt;&#x2F;strong&gt; estimate.
&lt;ul&gt;
&lt;li&gt;What&#x27;s your guess about how long this item will take?&lt;&#x2F;li&gt;
&lt;li&gt;Aim for 50% confidence on these tasks. Half the time you do this task, it should take longer. The other half, it should take shorter.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;strong&gt;worst-case&lt;&#x2F;strong&gt; estimate.
&lt;ul&gt;
&lt;li&gt;If everything went wrong—broken builds, libraries not having needed features, unexpectedly-complex code—how long would it take? I like to use the guideline of &quot;how long would this have to take before we would choose to take a different approach?&quot;&lt;&#x2F;li&gt;
&lt;li&gt;This should almost always be quite a bit longer than the normal-case estimate.&lt;&#x2F;li&gt;
&lt;li&gt;Aim for 90% confidence here. (If you did this task 10 times, 9 of them would take less time than the worst-case estimate.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;strong&gt;best-case&lt;&#x2F;strong&gt; estimate.
&lt;ul&gt;
&lt;li&gt;If someone got into some amazing flow state and everything magically fell into place, how long would it take?&lt;&#x2F;li&gt;
&lt;li&gt;This should always be shorter than the normal-case estimate, although for very short tasks it may not be &lt;em&gt;much&lt;&#x2F;em&gt; shorter.&lt;&#x2F;li&gt;
&lt;li&gt;Aim for 90% confidence again here. (If you did this task 10 times, 9 of them would take more time than the best-case estimate.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It will probably help to put these numbers in a spreadsheet!&lt;&#x2F;p&gt;
&lt;p&gt;Even though it&#x27;s a bit more work, this is the best place to get your team involved. You&#x27;ll find out areas that need to be broken down further, as well as areas that you thought would be difficult but maybe won&#x27;t be. These are some of my favorite discussions when planning, since it gets assumptions into the open where we can deal with them, and reveals differences in opinion between different team members.&lt;&#x2F;p&gt;
&lt;p&gt;This might seem like a lot, but it usually goes quickly. Don&#x27;t skip all three numbers, either: if you ask people for just one number you&#x27;ll usually get something that&#x27;s closer to the best-case estimate than the normal case. As we&#x27;ll see in a minute, that&#x27;ll mean an overly-optimistic final estimate!&lt;&#x2F;p&gt;
&lt;p&gt;For our example, let&#x27;s say we talked with our team and got these estimates:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Item&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Worst Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Normal Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Best Case&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Upgrade post component to new standard pattern&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add reaction button, styles, event handlers with DOM tests&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Test for keyboard and screen reader accessibility&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.5 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 hours&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add API endpoint to handle reaction POST&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add database table to store many-to-many relationship between posts+users with reaction data.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 hour&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;building-an-estimate&quot;&gt;Building an Estimate&lt;&#x2F;h2&gt;
&lt;p&gt;Finally we&#x27;re getting to the point where we can produce something other people would recognize as an estimate!&lt;&#x2F;p&gt;
&lt;p&gt;First, we&#x27;ll need to calculate the &quot;expected case.&quot; In this style of estimation, that&#x27;s pretty simple: just sum up the normal case times. If you&#x27;ve kept to a standard of 50% confidence for these, you&#x27;re doing well! For the part of this project we&#x27;re using as an example, we get 4 days 6 hours (which I&#x27;m choosing to call 4.75 days for maths&#x27;s sake.)&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Item&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Worst Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Normal Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Best Case&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Upgrade post component to new standard pattern&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add reaction button, styles, event handlers with DOM tests&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Test for keyboard and screen reader accessibility&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.5 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 hours&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add API endpoint to handle reaction POST&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add database table to store many-to-many relationship between posts+users with reaction data.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 hour&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Total&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;12.5 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.75 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.375 days&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Our next step will be to turn this into a range by getting the standard deviation and then using it to create a percent-confidence-based estimate.&lt;&#x2F;p&gt;
&lt;p&gt;If you have fewer than 10 tasks&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#law-of-large-numbers&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, this math is pretty easy: &lt;code&gt;stddev = (sum(worstCase) - sum(bestCase)) &#x2F; 3.3&lt;&#x2F;code&gt;. (You&#x27;re just going to have to trust me on the magic constant for now!)&lt;&#x2F;p&gt;
&lt;p&gt;In our case, that&#x27;s &lt;code&gt;(12.5 - 2.375) &#x2F; 3.3&lt;&#x2F;code&gt;, or 3.07.&lt;&#x2F;p&gt;
&lt;p&gt;However, if you have 10 tasks or more it&#x27;s better to calculate &lt;code&gt;(worstCase - bestCase) &#x2F; 3.3&lt;&#x2F;code&gt; for each individual task, then square it to get the variance. To get the final standard deviation, we&#x27;ll take the square root of the sum of the variances. Like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Item&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Worst Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Normal Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Best Case&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Stddev&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Variance&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Upgrade post component to new standard pattern&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.756&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.574&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add reaction button, styles, event handlers with DOM tests&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.909&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.826&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Test for keyboard and screen reader accessibility&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.5 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.379&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.143&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add API endpoint to handle reaction POST&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 day&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.455&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.207&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add database table to store many-to-many relationship between posts+users with reaction data.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 days&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 hours&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 hour&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.568&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.323&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;That&#x27;s a total variance of 2.073, for a final standard deviation of 1.440.&lt;&#x2F;p&gt;
&lt;p&gt;(That&#x27;s pretty different than the group formula&#x27;s answer of 3.07; that&#x27;s because with few data points we can&#x27;t take advantages of the law of large numbers and have to assume that we&#x27;re 90% confident in the entire data set instead of in each individual point.)&lt;&#x2F;p&gt;
&lt;p&gt;Finally, you take your expected case and compute a range based on what percentage confidence you want to communicate:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Percentage Confident&lt;&#x2F;th&gt;&lt;th&gt;Calculation&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;2%&lt;&#x2F;td&gt;&lt;td&gt;expected - 2 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;10%&lt;&#x2F;td&gt;&lt;td&gt;expected - 1.28 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;16%&lt;&#x2F;td&gt;&lt;td&gt;expected - standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;20%&lt;&#x2F;td&gt;&lt;td&gt;expected - 0.84 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;25%&lt;&#x2F;td&gt;&lt;td&gt;expected - 0.67 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;30%&lt;&#x2F;td&gt;&lt;td&gt;expected - 0.52 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;40%&lt;&#x2F;td&gt;&lt;td&gt;expected - 0.25 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;50%&lt;&#x2F;td&gt;&lt;td&gt;expected&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;60%&lt;&#x2F;td&gt;&lt;td&gt;expected + 0.25 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;70%&lt;&#x2F;td&gt;&lt;td&gt;expected + 0.52 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;75%&lt;&#x2F;td&gt;&lt;td&gt;expected + 0.67 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;80%&lt;&#x2F;td&gt;&lt;td&gt;expected + 0.84 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;84%&lt;&#x2F;td&gt;&lt;td&gt;expected + standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;90%&lt;&#x2F;td&gt;&lt;td&gt;expected + 1.28 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;98%&lt;&#x2F;td&gt;&lt;td&gt;expected + 2 * standard deviation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;So for our project, using the individual standard deviation calculation, we&#x27;d get a 90% confident estimate by calculating &lt;code&gt;4.75 + 1.28 * 1.44&lt;&#x2F;code&gt;, or 6.59 days. Filling out the table, we get:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Percentage Confident&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Estimate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;2%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.87&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;10%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.91&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;16%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.31&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;20%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.54&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;25%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.79&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;30%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.00&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;40%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.38&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;50%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.75&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;60%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.11&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;70%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.50&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;80%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.96&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;84%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6.19&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;90%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6.59&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;98%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7.63&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;communicating-the-estimate&quot;&gt;Communicating the Estimate&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ve all heard Spock or a robot or whatever say things like &quot;there is a 95.83462% chance of failure!&quot; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tvtropes.org&#x2F;pmwiki&#x2F;pmwiki.php&#x2F;Main&#x2F;LudicrousPrecision&quot;&gt;There&#x27;s even a TV Tropes page on it.&lt;&#x2F;a&gt; But when you&#x27;re communicating estimates? Don&#x27;t do that!&lt;&#x2F;p&gt;
&lt;p&gt;We can calculate our estimates to whatever precision we want. Since our units are days, calculating to the hundredths place means we&#x27;re claiming a precision of 14 minutes and 24 seconds.&lt;&#x2F;p&gt;
&lt;p&gt;This seems reasonable until you remember that our inputs are… well, if you&#x27;re doing this process for the first time, let&#x27;s call them &quot;fuzzy.&quot; Even when you&#x27;re trying to get to 90% accuracy, you might get lucky and hit 70% the first time.&lt;&#x2F;p&gt;
&lt;p&gt;But at this point, you do need to give someone a number—boss, PM, whoever. My usual way to do it: take the 50th percentile and the 98th percent confidence numbers&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#why-50-and-98&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and put them into human language.&lt;&#x2F;p&gt;
&lt;p&gt;I want to justify each step here, so:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;We expect this to take between 4.75 and 7.63 days.&quot; Precise but no good, since whoever you&#x27;re giving this too will hear accuracy when you only intended to communicate precision. So…&lt;&#x2F;li&gt;
&lt;li&gt;&quot;We expect this to take between 5 and 8 days.&quot; Better, and if I were communicating internally (e.g. with a PM assigned to the team) I might stop here. Note that I rounded up; rounding down to 4 days would take us from 50% confident to 30%. Overall, however, it&#x27;s still a fairly narrow range. I might improve it to…&lt;&#x2F;li&gt;
&lt;li&gt;&quot;We expect this to take 1–2 weeks.&quot; We&#x27;re 90% confident this work will take &lt;em&gt;at least&lt;&#x2F;em&gt; 4.75 days, so rounding up to a workweek is not unreasonable. And 8 workdays definitely represents the better part of two workweeks.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This is different than the kinds of random padding people do with off-the-cuff estimates (&quot;engineers always pad by 20%; I&#x27;ll cut it by that much&quot;) since we&#x27;re working from reasonable estimates for individual items. However, we know we&#x27;re not going to be 100% accurate, or even accurate to within a tenth of a day in many cases. It&#x27;s reasonable to represent the fuzziness in our inputs with fuzziness in our outputs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;adjusting-for-inaccuracy&quot;&gt;Adjusting for Inaccuracy&lt;&#x2F;h2&gt;
&lt;p&gt;As you proceed through the project, you&#x27;re going to immediately get useful data about how accurate your estimates were. The evidence shows up immediately in your bug tracker: how long did it take between starting and finishing the task? I can tell you that you&#x27;re not gonna hit 100% of your estimates! Fortunately, we can account for that.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s talk about where we got the magic divisor 3.3 from. That&#x27;s actually the amount of standard deviations that you think the range spans, and it depends on the percentage confidence you are trying to hit during the estimation process. Since I told you to aim for 90%, we use 3.3. Here are some more:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Actual Outcomes within Range&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Divisor for standard deviations&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;10%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.25&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;20%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.51&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;30%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.77&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;40%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;50%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;60%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.7&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;70%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;80%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.6&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;90%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;99.7%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;This works in reverse, too: if we find that we&#x27;ve been inaccurate in our estimates as the project progresses, we can recalculate the estimate based on our real accuracy.&lt;&#x2F;p&gt;
&lt;p&gt;So let&#x27;s pretend that we&#x27;re done with the part of the project we&#x27;ve been estimating so far and have hit a milestone. Hooray and congrats! Remembering that this is just the first part of the project, let&#x27;s update the estimate for the rest.&lt;&#x2F;p&gt;
&lt;p&gt;Say we&#x27;ve got 15 tasks and our actual working time fell within the bounds of the estimate for 12 of them. That ends up being 80% coverage, not 90%. Redoing the calculations, we get a standard deviation of 1.83, up from 1.44. That&#x27;s not a huge jump, but it&#x27;s enough to know that we need to widen our range. If we were to recalculate our sample tasks, the most precise version of the range would go from 4.75–7.63 days to 6.58–8.41 days.&lt;&#x2F;p&gt;
&lt;p&gt;This may be an unpleasant surprise to your stakeholders, but it&#x27;s better to share this kind of news early instead of waiting until the very end of the project to drop that everything is going to take 25% longer than you originally estimated. Point is, you now have the tools to adjust! In the second project you estimate with this method, you&#x27;ll account for more of the things that you missed the first time.&lt;&#x2F;p&gt;
&lt;p&gt;Because you know things will shift around as you get more data, you should agree with your stakeholders that you&#x27;ll be providing updates on the estimate as you move through the project. If they&#x27;re expecting to hear from you, delivering that news gets way easier (and it&#x27;s not that much additional work if you&#x27;ve kept the spreadsheet from earlier around.)&lt;&#x2F;p&gt;
&lt;p&gt;You can also begin in a better place once you have some historical data. If you know that your team almost always ends up at 70%, you can use that to calculate your initial estimate. (You should still work on identifying gaps and building skill, though, so that you can get to 90%!)&lt;&#x2F;p&gt;
&lt;p&gt;Basically, as soon as you can ground your estimate in reality, do that!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;that-s-it&quot;&gt;That&#x27;s It!&lt;&#x2F;h2&gt;
&lt;p&gt;Just to get everything in one place, here are the steps we&#x27;ve just been over:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Break down work into high-level areas.&lt;&#x2F;li&gt;
&lt;li&gt;Break those areas down until each task feels like something you&#x27;d put in a PR.&lt;&#x2F;li&gt;
&lt;li&gt;Work with your team to estimate the best-case, normal-case, and worst-case scenarios for each task.&lt;&#x2F;li&gt;
&lt;li&gt;Figure out the standard deviation represented by the range between best and worst case, then use it to calculate a time range that you can communicate. (50% and 98% is nice.)&lt;&#x2F;li&gt;
&lt;li&gt;Re-estimate at least at agreed-upon milestones. If you have to share bad news, share it quickly so that your team can get ahead of it and build trust and credibility.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If you&#x27;ve enjoyed this, or want to learn more, I&#x27;d really recommend picking up &lt;em&gt;Software Estimation: Demystifying the Black Art&lt;&#x2F;em&gt;. In addition to this technique (which is based off of several chapters, but most notably chapter 10) there&#x27;s a lot about ways to build up an estimate based off analogies to historical data, as well as useful tactics for communicating with stakeholders.&lt;&#x2F;p&gt;
&lt;p&gt;Give this a try, though! I&#x27;d love to know how it turns out for you!&lt;&#x2F;p&gt;
&lt;p&gt;However, before I leave you…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reasonable-objections&quot;&gt;Reasonable Objections&lt;&#x2F;h2&gt;
&lt;p&gt;People tend to be skeptical of this process. Seems like a lot? For what benefit? So I want to address some of that:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;time-cost&quot;&gt;Time Cost&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This seems like a lot of effort? Is the improvement here really worth it?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I think so, yes! (Or I wouldn&#x27;t be writing about it, would I?)&lt;&#x2F;p&gt;
&lt;p&gt;Basically: as a team leader, this is work you&#x27;d be doing anyway. Even if you aren&#x27;t doing it directly, someone else (maybe your PM) is breaking down projects into smaller components that can be worked as tasks.&lt;&#x2F;p&gt;
&lt;p&gt;After that, it&#x27;s pretty common for a stakeholder to count the tasks&#x2F;stories&#x2F;points in the project and divide by the team&#x27;s velocity to get a simple number. This process provides benefits above and beyond that because:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;We&#x27;re breaking down work down past where an external stakeholder or PM would necessarily have expertise. Doing this requires you to build a good idea of the feature&#x2F;project&#x2F;requirements, and puts you in a position to be a good partner.&lt;&#x2F;li&gt;
&lt;li&gt;It gives your team a chance to come up to speed on the same, as well as creating opportunities for them to point out issues.&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s generally more accurate than a simple velocity calculation.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I talked a lot about the statistical machinery in this post since it&#x27;s important to use that correctly. However, a lot of the benefit of this process comes from the discussions you have to have to run it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;anchoring-bias&quot;&gt;Anchoring Bias&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;How do you run the estimation sessions? Don&#x27;t people anchor on the first number they hear? Doesn&#x27;t this process also bias towards the loudest or most senior person&#x27;s view?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This is sort of orthogonal to the estimation process, but it&#x27;s important!&lt;&#x2F;p&gt;
&lt;p&gt;First, I&#x27;ve played a game with people when running these meetings. Instead of just opening the floor and saying &quot;what do you think is the worst-case estimate here?&quot; I&#x27;ll ask people to show a number of fingers on the count of three. Then we have a discussion about why there&#x27;s a broad range, or simply accept the lowest&#x2F;highest answer for a narrow one (depending on if we&#x27;re doing worst-case or best-case.)&lt;&#x2F;p&gt;
&lt;p&gt;All that goes out the window, though, if a team has unhealthy group dynamics. If you have a single person or group dominating the conversation, you&#x27;re going to get a worse result. Fixing this is outside the scope of this post.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-about-fixing-50-confidence&quot;&gt;What about fixing 50% confidence?&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;How do you calibration 50% confidence for the normal case estimates? Seems like a tricky skill to learn. Most teams will be guessing.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Yep, most teams will be guessing at first. However, we&#x27;ll get data pretty quickly as to how well we guessed. Unfortunately, I don&#x27;t have a convenient statistical trick to adjust the expected case if you&#x27;re consistently ending up on one side of the line. However, that&#x27;s data that you can take into the next projects and learn from.&lt;&#x2F;p&gt;
&lt;p&gt;McConnell also has a number of strategies in the book that help increase accuracy here.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;identifying-gaps&quot;&gt;Identifying Gaps&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;You mentioned that the biggest inaccuracies have been due to missing work. How do you get better at that skill?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A combination of best practice and battle scars. After each gap, I&#x27;ve been collecting questions to ask to avoid the issue in the future. Again, my list is available at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;feature-proofing-checklist&#x2F;&quot;&gt;feature-proofing checklist&lt;&#x2F;a&gt; and I encourage you to steal it and make it your own! (I&#x27;ve given this to new team leaders as a handout for years, and it&#x27;s usually been pretty helpful!)&lt;&#x2F;p&gt;
&lt;p&gt;Some notes on mine:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It starts with a bunch of &quot;why&quot; questions. If you can&#x27;t answer those for your team, you&#x27;re not in a good place. This also helps push back on junk work that people haven&#x27;t thought through.&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s a lot of work, but this is another case where you should be doing most of this anyway.&lt;&#x2F;li&gt;
&lt;li&gt;You don&#x27;t need to answer all the questions up front or by yourself. The first section is work with your PM or stakeholders, the second is for gap coverage, and the third has a few questions for nearer the tail end of the project.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;what-if-your-estimate-is-unacceptably-long&quot;&gt;What if your estimate is unacceptably long?&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;What if someone expects a feature to take 3 weeks and you come back with a 3-month estimate?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Yeah, this happens. It&#x27;s painful, but a good opportunity to make implicit assumptions explicit. While it can be annoying to have to negotiate&#x2F;cut scope, that&#x27;s part of the job sometimes too.&lt;&#x2F;p&gt;
&lt;p&gt;In my experience, this only has to happen once or twice before people start asking your opinion much earlier in the process. So there&#x27;s a virtuous cycle here as well, in terms of doing a good job on engineering leadership!&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;cassandra&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Of course, it&#x27;s possible that you&#x27;re in a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Cassandra&quot;&gt;Cassandra&lt;&#x2F;a&gt; situation and credibility and accuracy aren&#x27;t the problems. If that&#x27;s the case, I&#x27;ve been there and this process isn&#x27;t the way out. Sorry!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;lake-michigan&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;about 6 trillion US (short) tons, or 5 trillion metric tons.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;law-of-large-numbers&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;If you have fewer than 10 items in your list, you may want to consider breaking them down more. At 10 items and above you start to benefit from the law of large numbers, where errors in your estimates will start cancelling out.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;why-50-and-98&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;Why 50% and 98%? Two reasons. First, people are generally happy with &quot;whether we finish early is basically a coin flip, but you can make plans around hitting the end date.&quot; Second, I can remember the calculations off the top of my head: 50% is &lt;code&gt;expected + stddev&lt;&#x2F;code&gt; and 98% is &lt;code&gt;expected + 2 * stddev&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Horizon Investments</title>
		<published>2025-12-03T00:00:00+00:00</published>
		<updated>2025-12-03T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/horizon/" type="text/html"/>
		<id>https://bytes.zone/projects/horizon/</id>
		<content type="html">&lt;p&gt;In December 2025, I joined &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.horizoninvestments.com&#x2F;&quot;&gt;Horizon Investments&lt;&#x2F;a&gt; as a software engineer.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Should we design for iffy internet?</title>
		<published>2025-06-16T00:00:00+00:00</published>
		<updated>2025-06-16T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/should-we-design-for-iffy-internet/" type="text/html"/>
		<id>https://bytes.zone/posts/should-we-design-for-iffy-internet/</id>
		<content type="html">&lt;p&gt;I keep hearing claims like this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Not everyone in the US has access to stable, reliable internet, even in 2025.&lt;&#x2F;li&gt;
&lt;li&gt;Web developers should stop assuming people have fast internet connections and slim their payloads accordingly.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This seems intuitively true to me—programmers are gonna have better connectivity because that takes money, and programmers are well-paid. But what&#x27;s the actual scope of the problem?&lt;&#x2F;p&gt;
&lt;p&gt;I dug around, and here&#x27;s some data. My goal here is not to beat anyone over the head with &quot;THOU SHALT NOT ASSUME GOOD INTERNET&quot; but to give an idea about the scope of broadband rollout in the US in a way that can help inform choices we make when designing software.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;If you don&#x27;t feel like reading this whole thing, here&#x27;s the bottom line up front:&lt;&#x2F;strong&gt; you can probably assume internet access in somewhere around 97% of US households, but you should not assume that it&#x27;s better than around 25Mbps down and 3Mbps up, and latency may be significantly worse than you previously assumed. This is likely worse for B2C software than B2B.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;I&#x27;m going to pull from two US government agencies here: the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fcc.gov&quot;&gt;Federal Communications Commission (FCC)&lt;&#x2F;a&gt;and the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nces.ed.gov&quot;&gt;National Center for Education Statistics (NCES)&lt;&#x2F;a&gt; (part of the Department of Education.) All the data I&#x27;m referencing was published well before the current administration started gutting the bureaucracy, so I think it&#x27;s fairly reliable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;assumptions&quot;&gt;Assumptions&lt;&#x2F;h2&gt;
&lt;p&gt;Before we begin, there are a bunch of ways to define &quot;stable&quot; or &quot;reliable&quot; internet connections. For the purposes of this post, I&#x27;m defining that as a &lt;strong&gt;terrestrial link with at least 25Mbps down and 3Mbps up.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Terrestrial because—well, have you ever tried to use a satellite connection for anything real? Latency is awful, and the systems tend to go down in bad weather. They also have had fairly low data caps historically so you had to use your connection judiciously, although this may be changing.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#satellite&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#my-history&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;li&gt;
&lt;li&gt;25&#x2F;3Mpbs because that&#x27;s a fairly common cable package speed, and also around the minimum I’ve been able to use modern SaaS apps on without it getting ridiculously frustrating. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;support.zoom.com&#x2F;hc&#x2F;en&#x2F;article?id=zm_kb&amp;amp;sysparm_article=KB0060748#h_d278c327-e03d-4896-b19a-96a8f3c0c69c&quot;&gt;Zoom says&lt;&#x2F;a&gt; you need 1–4 Mbps in both directions to do a video call.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The FCC&#x27;s broadband map lets you set both these criteria. Let&#x27;s have a look at it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-fcc-broadband-map&quot;&gt;The FCC Broadband Map&lt;&#x2F;h2&gt;
&lt;p&gt;The FCC tracks access to broadband internet in the US. As a result, they publish a map that&#x27;s more accurate than the maps you see from ISPs. This isn&#x27;t &lt;em&gt;completely&lt;&#x2F;em&gt; error-free, but individuals and companies can both submit data on coverage at a street address level and challenge inaccuracies in the existing data. For our purposes, I think it&#x27;s good enough!&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m going to grab some screenshots of the map to fix it at the current data for discussion. That way we can get an idea about regional coverage.&lt;&#x2F;p&gt;
&lt;p&gt;Strangely, they don&#x27;t let you zoom out enough to grab a screenshot of the whole country so I&#x27;m going to look at the west. That&#x27;ll get both urban and rural coverage, as well as several famously internet-y locations (San Francisco Bay Area, Seattle.)&lt;&#x2F;p&gt;
&lt;p&gt;If you want to follow along or compare how the data has changed since this post was published, you can get the map yourself at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;broadbandmap.fcc.gov&quot;&gt;broadbandmap.fcc.gov&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;First, here&#x27;s a hex map of coverage using the criteria we set above. The darker each hex is, the higher the percent coverage within it. Dark blue is 100%, white is 0%, grays or faded blues are in between.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;us-national-broadband-map-terrestrial-25-3-2025-06-10.png&quot; alt=&quot;A map of the western US with a color scale overlaid showing access to broadband. Higher-population areas are shown to have better access, generally, with the exception of the Dakotas which have excellent access throughout the states.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This shows pretty much what I&#x27;d expect: coverage is fine in and around cities and less great in rural areas.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#mobile&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; (The Dakotas are an interesting exception; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.bek.coop&#x2F;&quot;&gt;there&#x27;s a co-op up there&lt;&#x2F;a&gt; that connected a ton of folks with gigabit fiber. Pretty cool!)&lt;&#x2F;p&gt;
&lt;p&gt;Although, remember that these are &lt;em&gt;minimum&lt;&#x2F;em&gt; criteria. When I&#x27;m building software, I&#x27;m mostly doing it on my home internet. A test just now says I get 367&#x2F;71Mpbs. What does the country look like if I were to expect all my users to have similar connections? The map lets us filter for 250&#x2F;25; let&#x27;s look:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;us-national-broadband-map-terrestrial-250-25-2025-06-10.png&quot; alt=&quot;A map of the western US with a color scale overlaid showing access to broadband at a higher level. Rural areas show much less availability than in the previous map, although access in the Dakotas is still excellent.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Cities are pretty much unchanged, but rural coverage gets much worse. (Except again for North Dakota, the reigning champion of rural fiber.)&lt;&#x2F;p&gt;
&lt;p&gt;So what does this tell us about how we should design software? One big takeaway: if you design for the &lt;em&gt;availability&lt;&#x2F;em&gt; of fast internet connections, you&#x27;ll exclude many people in rural areas.&lt;&#x2F;p&gt;
&lt;p&gt;This may or may not be OK for your market—&quot;good internet&quot; tends to be in population centers, and population centers tend to contain more businesses and consumers. You have to make that call!&lt;&#x2F;p&gt;
&lt;p&gt;However, it&#x27;s also worth keeping in mind that this is a map of commercial availability, not market penetration. Hypothetically, you could get the average speed of a US residential internet connection, but the FCC doesn&#x27;t make such a statistic available.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#speed-stat-third-party&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;However, we have another source of data!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;student-internet-access&quot;&gt;Student Internet Access&lt;&#x2F;h2&gt;
&lt;p&gt;The US Department of Education occasionally tracks student internet access. Since classwork and homework have moved more online (especially during the early days of COVID) it&#x27;s useful data for policy-making. The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.thefreelibrary.com&#x2F;Relationship+Between+Internet+Access+and+Literacy+Among+OECD...-a0795707532&quot;&gt;most recent data I could find is from 2021&lt;&#x2F;a&gt; but it gives us both a baseline for internet availability and some demographic data. Unfortunately, they don&#x27;t track bandwidth, just raw availability. Still useful, though.&lt;&#x2F;p&gt;
&lt;p&gt;Here are the top-line statistics for all students in the US across several versions of the report:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Category&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;% Available 2019&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;% Available 2021&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Difference&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Any internet access&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;94.6%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;97.1%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+2.5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Smartphone-only&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6.5%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.5%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-2.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;No internet Access&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.4%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.9%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-2.5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;In 2021, they say that these statistics cover 66,108,000 students, which means 2.97 million students only had mobile access as of 2021.&lt;&#x2F;p&gt;
&lt;p&gt;Access gets worse, of course, with lower household income. Here&#x27;s how those stats look for the lowest quartile:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Category&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;% Available 2019&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;% Available 2021&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Difference&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Any internet access&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;88.6%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;94.4%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+5.8&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Smartphone-only&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.1%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.8%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-4.3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;No internet access&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.4%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.6%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-5.8&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Despite the fact that these numbers are going down, that&#x27;s still a huge number of people in absolute terms (1.87 million students in this category with only mobile access.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-do-we-do-with-this-information&quot;&gt;What Do We Do With This Information?&lt;&#x2F;h2&gt;
&lt;p&gt;So that&#x27;s a lot of words to say this: despite gains in the last couple of years, it&#x27;s still not safe to assume that every user of your software either has access to stable internet, or is willing and able to pay to get high speeds.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I&#x27;m deliberately not making any moral judgments here. If you think you&#x27;re in a situation where you can ignore this data, I&#x27;m not going to come after you. But if you dismiss it out of hand, you&#x27;re likely going to be putting your users (and business) in a tough spot.&lt;&#x2F;p&gt;
&lt;p&gt;I think it&#x27;s worth considering a couple of scenarios in the parts of your software that someone interacts with regularly:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;What if that person is on a slow link? If you&#x27;ve never had bad internet access, maybe think of this as plane wifi. Rural satellite connections behave very similarly: high latency, speed and (sometimes) low data caps.&lt;&#x2F;li&gt;
&lt;li&gt;What if that person is on a mobile&#x2F;metered&#x2F;capped network? Remember that 4G is like 5&#x2F;1Mbps, and 3G is even worse. Big downloads are probably not a great idea.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This is also a very US-centric view, plus it doesn&#x27;t consider latency from distance between your data center and your user&#x27;s device. Still, though, I think this shows that this problem is real and we should take it into account when designing software.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;satellite&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Despite these drawbacks, satellite has high availability because you just need to have access to power instead of laying cable or building towers. It&#x27;s still the best option for a lot of the rural US.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;my-history&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;I grew up in a semi-rural area (mountains) and this was the only real option. As a kid, I really hated it! The latency was too bad to play games with my friends online, and I also regularly hit our household&#x27;s download cap and got us all throttled to basically-dialup for the rest of the month.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;mobile&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;I&#x27;m not going to embed a screenshot, but mobile coverage looks pretty much the same if you set the same bandwidth requirements. According to the FCC, that&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;5G_NR&quot;&gt;5G-NR&lt;&#x2F;a&gt; at 35&#x2F;3Mbps. 4G has far better coverage in rural areas, but only gets 5&#x2F;1Mbps.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;speed-stat-third-party&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;The ones published by various speed testing companies vary so wildly that I don&#x27;t think they&#x27;re worth paying attention to, even for broad decision-making.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>LFFS update, June 2025</title>
		<published>2025-06-15T00:00:00+00:00</published>
		<updated>2025-06-15T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-007/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-007/</id>
		<content type="html">&lt;p&gt;It&#x27;s been a while since I&#x27;ve written anything here, especially about &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;local-first-from-scratch&#x2F;&quot;&gt;Local-First from Scratch&lt;&#x2F;a&gt;. Here&#x27;s what I&#x27;ve been up to:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ul&gt;
&lt;li&gt;I took a little break (just a couple weeks) to learn about MiniZinc. Mainly I learned that it&#x27;s a very opaque system… even getting a problem optimized enough to even be solved is challenging (at least at my skill level.)&lt;&#x2F;li&gt;
&lt;li&gt;Signed up for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.raptitude.com&#x2F;2024&#x2F;08&#x2F;do-quests-not-goals&#x2F;&quot;&gt;One Big Win&lt;&#x2F;a&gt; as a framework for writing.&lt;&#x2F;li&gt;
&lt;li&gt;During OBW (8 weeks), I did a lot of research to backfill the theoretical fundamentals of my writing work, as well as breaking ground on a new implementation.&lt;&#x2F;li&gt;
&lt;li&gt;Since OBW, I&#x27;ve been continuing with the implementation.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;At this point, I&#x27;ve got maybe 25% of the code I need to write an actually-decent version of the book. It&#x27;s going slow-ish, so I&#x27;m trying to reserve some time to write. Hopefully I can start publishing more regularly here!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>a dilemma, or is it a conundrum? Maybe it&#x27;s a dilendrum!</title>
		<published>2025-02-27T00:00:00+00:00</published>
		<updated>2025-02-27T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-006/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-006/</id>
		<content type="html">&lt;p&gt;So I thought I had a pretty good thing going: in my last post, I documented some &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;lffs-005&#x2F;&quot;&gt;early results from the time-tracking software that I wrote for &lt;em&gt;Local-First From Scratch&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. I thought I was on track for an easy conversion to prose, then an easy beta-reading, then becoming a thousandaire and retiring to my &lt;del&gt;private island&lt;&#x2F;del&gt; couch to drink a budget mojito or whatever.&lt;&#x2F;p&gt;
&lt;p&gt;But then… I committed to giving a talk to a local meetup group about implementing CRDTs in Rust.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Simple!&quot;, I thought, &quot;I can just apply the stuff I&#x27;ve already written to a new domain and it&#x27;ll all work out.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Well… no, not so much.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;It turns out that the time tracker, while valuable, can be represented almost entirely with extremely simple CRDTs. I think a last-write-wins register based on a hybrid logical clock is the most complex thing you need. That&#x27;s not exactly CRDTs 101… but, it is like CRDTs 102.&lt;&#x2F;p&gt;
&lt;p&gt;This is not necessarily a bad thing, of course: it doesn&#x27;t make the time tracker any less valuable &lt;em&gt;as software I use&lt;&#x2F;em&gt;, but it does mean that if you learn just enough to make it, then you have not learned enough to make something that acts like a normal CRUD app with records. Whoops! If I stop there, the book will not generalize to the problems that the reader actually might have to work through.&lt;&#x2F;p&gt;
&lt;p&gt;In this meetup talk, I found this out because I thought through implementing &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;todomvc.com&#x2F;&quot;&gt;TodoMVC&lt;&#x2F;a&gt; (really the table stakes of client-side CRUD apps.) This is specifically because in addition to completing or editing the description of todos, we can also archive completed todos from a key-value map.&lt;&#x2F;p&gt;
&lt;p&gt;If you know anything about CRDTs, you may have just gone &quot;ahhh, Brian, I see your problem.&quot; If you don&#x27;t, here&#x27;s the deal: in order to store key removals efficiently, you have to store set removals efficiently (because the keys of a map are a set.) In order to store set removals efficiently, you need to use what some folks call an Observed Remove Set Without Tombstones (ORSWOT.) As the name implies, it doesn&#x27;t store tombstones—which most other add&#x2F;remove sets do!&lt;&#x2F;p&gt;
&lt;p&gt;Implementing an ORSWOT is not hard, &lt;em&gt;per se&lt;&#x2F;em&gt;, but it requires explaining a lot more of the mechanics of CRDTs than I had planned to in the book. In particular, now I need to explain vector clocks, dot context, dot kernels, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Again, none of these are hard on their own, but taken together it&#x27;s a lot of work that I haven&#x27;t needed to do for the book as it&#x27;s written so far.&lt;&#x2F;p&gt;
&lt;p&gt;So that leaves me needing a slightly more complex example for the book. And, I don&#x27;t know… is TodoMVC interesting enough as an example to hold people&#x27;s attention? Or, worse: does the fact that so much is required to make TodoMVC into a local-first syncable app mean that I&#x27;m implicitly telling folks &quot;this seemingly-simple thing is actually super complex. Abandon hope, all ye who enter here.&quot;?&lt;&#x2F;p&gt;
&lt;p&gt;I think the way to thread the needle might be to make an app that&#x27;s &lt;em&gt;nominally&lt;&#x2F;em&gt; more complex than TodoMVC, but not so much as to make the implementation become unmanageable in the prose. Just something to say &quot;oh yeah it&#x27;s not so simple as TodoMVC… just a little more complex so we can see the full power of this thing.&quot; Maybe having projects as well as todos? Or dependencies? Or making a contact app with various forms of contact information? Or something else entirely?&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not sure yet. I&#x27;m leaning towards TodoMVC + some wrinkle, but if you have an opinion, then please email me. I&#x27;d love to hear it.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>some time tracking results</title>
		<published>2025-01-29T00:00:00+00:00</published>
		<updated>2025-01-29T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-005/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-005/</id>
		<content type="html">&lt;p&gt;A while back I wrote (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-03-01&#x2F;&quot;&gt;post 1&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-03-02&#x2F;&quot;&gt;post 2&lt;&#x2F;a&gt;) about how TagTime (and the then-called TinyPing) analyze time by assuming that each ping is worth 45 minutes, then getting a daily average and a 95% confidence interval. This can give you a pretty good idea of how you&#x27;re spending your time, but I only did it for a simulated person with a perfect schedule.&lt;&#x2F;p&gt;
&lt;p&gt;If you haven&#x27;t read about this before, here&#x27;s the basic idea:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The system randomly asks you what you&#x27;re doing.&lt;&#x2F;li&gt;
&lt;li&gt;It biases that random choice in a way that the long-term average time between pings is 45 minutes (or whatever you like)&lt;&#x2F;li&gt;
&lt;li&gt;Eventually, you can get an idea of what a &quot;normal&quot; day looks like by doing a little math.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Anyway, let&#x27;s do the analysis now that I&#x27;ve got a bit over a month of data.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;What you&#x27;ll see below:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The &quot;tag&quot; is what I&#x27;m doing. This is a dot-separated hierarchy (e.g. &lt;code&gt;work.meeting.standup&lt;&#x2F;code&gt;.) For analysis, I split that into three tags (&lt;code&gt;work&lt;&#x2F;code&gt;, &lt;code&gt;work.meeting&lt;&#x2F;code&gt;, and &lt;code&gt;work.meeting.standup&lt;&#x2F;code&gt;) so I can see the proportions in different categories.&lt;&#x2F;li&gt;
&lt;li&gt;The average daily time is the percentage representation of the tag in the data set times the number of minutes in the day. (E.g. if I had 100 pings, 30 of which were tagged &lt;code&gt;sleep&lt;&#x2F;code&gt;, I&#x27;d have &lt;code&gt;30 &#x2F; 100 * 1440&lt;&#x2F;code&gt; to get 432 minutes, or 7h12m.)&lt;&#x2F;li&gt;
&lt;li&gt;The margin of error is a 95% confidence interval of the average daily time, rounded to the nearest minute.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In this data, I&#x27;ve censored a few things—either because they felt too personal to share in a space like this or because they reveal the contents of work projects—but otherwise I&#x27;ve checked this against other accurate sources I have (e.g. my watch for sleep tracking) and it all seems to be accurate!&lt;&#x2F;p&gt;
&lt;p&gt;So, here are the top places I&#x27;ve been spending my time recently:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;th&gt;Average Daily Time ± Margin of Error&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Unknown&lt;&#x2F;td&gt;&lt;td&gt;7h17m ± 43m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;sleep&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;6h58 ± 43m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;work&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;2h56m ± 31m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;beeps&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;48m ± 17m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;28m ± 13m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;lunch&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;25m ± 12m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;work.meeting&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;24m ± 12m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;breakfast&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;16m ± 10m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;k8s&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;14m ± 9m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;coffee&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;12m ± 9m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;driving&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;9m ± 8m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;dishes&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;8m ± 7m&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;There are a lot of improvements I could make here. For example, I&#x27;ve had a lot of time off recently due to holidays. If I applied that insight and re-analyzed &lt;code&gt;work&lt;&#x2F;code&gt; for only weekdays I worked, I&#x27;m sure I&#x27;d get more like 7.5 to 8 hours per day. Overall, though, I&#x27;m pretty happy with this level of insight!&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d like to try this for yourself, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bytes-zone&#x2F;beeps&quot;&gt;you can get the source or pre-built binaries on GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Local-First Foundations</title>
		<published>2025-01-22T00:00:00+00:00</published>
		<updated>2025-01-22T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/local-first-foundations/" type="text/html"/>
		<id>https://bytes.zone/posts/local-first-foundations/</id>
		<content type="html">&lt;p&gt;I&#x27;m writing a book about local-first software tentatively called &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;local-first-from-scratch&#x2F;&quot;&gt;Local-First From Scratch&lt;&#x2F;a&gt;. This is a snippet from that book—an intro to why you should care about local-first software and what it gets you. I thought it ended up being a pretty good summary of what the whole project is about, so I wanted to share it as a blog post as well!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;If you picked up this book, you might have some idea about what &quot;local-first&quot; means&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#lf-manifesto&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, but let&#x27;s get on the same page: other than &quot;works offline&quot;, what are we aiming for?&lt;&#x2F;p&gt;
&lt;p&gt;In short, local-first software moves ownership of data from &quot;somewhere in the cloud&quot; to your local devices.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;This has a bunch of consequences. We like some of them:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Updates are practically instant because the network does not have to be involved. No loading times or spinners! You can also work completely offline.&lt;&#x2F;li&gt;
&lt;li&gt;You are in control of your data and control who gets access to it.&lt;&#x2F;li&gt;
&lt;li&gt;A company going through an &quot;incredible journey&quot; acquihire doesn&#x27;t mean you lose access to your work.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Of course, &lt;em&gt;implementing&lt;&#x2F;em&gt; these ideas is a little harder than just storing data in a local SQLite database. There&#x27;s a cost paid in complexity and figuring out new ways of doing things. For example, we&#x27;d typically enable sharing by having a central server hold all the state. But we can&#x27;t do that if the user is offline and can&#x27;t reach the server!&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#local-first-business-case&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; So local-first also raises some questions we need to answer:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;You probably have more than one device (phone, computer, etc.) Which one is the &quot;real&quot; copy?&lt;&#x2F;li&gt;
&lt;li&gt;If you want to share your work with someone else, how?&lt;&#x2F;li&gt;
&lt;li&gt;When you share—either with other devices or other people—how do you avoid conflicting writes?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;We want satisfying answers to these without giving up local control. The typical approach—and the one we&#x27;ll implement in this book—is CRDTs.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;lf-manifesto&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;If you want to learn more about local-first principles, Ink &amp;amp; Switch&#x27;s essay &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.inkandswitch.com&#x2F;local-first&#x2F;&quot;&gt;&lt;em&gt;Local-first software: You own your data, in spite of the cloud&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; is a great place to keep going. You may hear folks refer to the &quot;local-first manifesto&quot;—this is the thing they&#x27;re talking about.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;local-first-business-case&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;This blows up a lot of SaaS business models that depend on a subscription to a central server. We&#x27;re not going to address the business ramifications of local-first software in this book. If you&#x27;re interested in that, as of early 2025 the Local-first Software Discord hosts frequent online meetups where people share their experiences.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;That&#x27;s it for the preview. Keep an eye out for the book hopefully later in 2025!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>LFFS: both a TUI and a GUI?</title>
		<published>2025-01-13T00:00:00+00:00</published>
		<updated>2025-01-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-004/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-004/</id>
		<content type="html">&lt;p&gt;I think I may need to change up the outline for Local-First from Scratch. Originally, I had this idea that it would be cool to make two separate clients that used the same sync code and data structures to demonstrate a modeling-first approach. &lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt; Now that I&#x27;m in the thick of it, though, I&#x27;m realizing that I don&#x27;t want to have to explain the incidental complexity of both a TUI and a GUI. One would be fine, but explaining how two separate event loops work might be a little much!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Depth Year - January</title>
		<published>2025-01-07T00:00:00+00:00</published>
		<updated>2025-01-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/depth-year-january/" type="text/html"/>
		<id>https://bytes.zone/micro/depth-year-january/</id>
		<content type="html">&lt;p&gt;David Cain wrote in 2017 about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.raptitude.com&#x2F;2017&#x2F;12&#x2F;go-deeper-not-wider&#x2F;&quot;&gt;a depth year&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;what if, for a whole year, you stopped acquiring new things or taking on new pursuits. Instead, you return to abandoned projects, stalled hobbies, unread books and other neglected intentions, and go deeper with them than you ever have before.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That really resonates with me! I have a terrible habit of making wishful acquisitions: things that I think will make my life better, then never actually doing them. Books are the main culprit here, but courses and projects in my task list are also frequent offenders.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;I think I&#x27;d like to give this a go! But I also feel cautious about new year&#x27;s resolutions because it&#x27;s hard to know how a whole year will go. I think quarterly or monthly resolutions are better: having 30 or 90 days creates fewer opportunities for procrastination than 365.&lt;&#x2F;p&gt;
&lt;p&gt;Within that theme, here are some things I&#x27;d like to go &quot;in depth&quot; on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;mental health (especially around mindfulness)&lt;&#x2F;li&gt;
&lt;li&gt;physical health (especially around calisthenics and mobility)&lt;&#x2F;li&gt;
&lt;li&gt;book writing&lt;&#x2F;li&gt;
&lt;li&gt;relationships (especially in the vein of deepening existing relationships)&lt;&#x2F;li&gt;
&lt;li&gt;ukulele&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;m having to stop myself here. There are a bajillion different things I could go on depth on, but focusing on all of them feels like it defeats the point.&lt;&#x2F;p&gt;
&lt;p&gt;For January, I think I&#x27;d like to focus on three (with success criteria stated up front):&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Physical health:&lt;&#x2F;strong&gt; one calisthenics workout per day for the month of January.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Book writing:&lt;&#x2F;strong&gt; material for the first beta reading session done by the end of January.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Ukulele:&lt;&#x2F;strong&gt; learn one complete song that I can play &quot;fluently&quot; (from memory, without pausing for chord changes, with good tone quality.)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;ll try to post a write-up of how it went in early February!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>early thoughts on using beeps</title>
		<published>2025-01-07T00:00:00+00:00</published>
		<updated>2025-01-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-003/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-003/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been using the TUI version of the book software for a couple days, and have some observations!&lt;&#x2F;p&gt;
&lt;p&gt;First off, here&#x27;s how it looks:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;beeps-0.3.0.png&quot; alt=&quot;a TUI for tagging pings running in a terminal with a blue background. The interface is two columns: one for ping times, and one for tags. Several tags are filled in, mostly with &amp;quot;sleep.&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Unlike my previous attempts at making this software, I can see and edit the whole database. I knew that would be helpful, but it&#x27;s way more helpful than I thought! For example, I no longer have to guess as much at what I previously tagged something. I can also go back and adjust without editing the file directly!&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m also finding that my memory of precisely what I&#x27;m doing can get spotty quickly. For example, I got a notification about a ping this morning and thought &quot;oh, I&#x27;ll tag that in a minute. It&#x27;s easy; I&#x27;m doing &lt;code&gt;X&lt;&#x2F;code&gt;!&quot; Ten minutes later, I went back and couldn&#x27;t for the life of me remember what &lt;code&gt;X&lt;&#x2F;code&gt; had been. I know it was something related to journaling, but not the exact details.&lt;&#x2F;p&gt;
&lt;p&gt;I guess that&#x27;s part of the point, though: removing the ability to gloss over time I spent unwisely is a major part of why I wanted this system to exist in the first place!&lt;&#x2F;p&gt;
&lt;p&gt;As I&#x27;ve added features to this TUI, though, it&#x27;s becoming less and less suited as the subject of a book that&#x27;s not &lt;em&gt;only&lt;&#x2F;em&gt; about making TUIs in Rust. I&#x27;m almost at the point of wanting to drop it for the book and make some simpler entry-only interface instead… problem is, I didn&#x27;t find the entry-only interface was very nice to use at all! Since part of this book is making good choices for product design like that, I don&#x27;t really want to go back to that.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>LFFS: Simplicity vs Efficiency</title>
		<published>2024-12-26T00:00:00+00:00</published>
		<updated>2024-12-26T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-002/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-002/</id>
		<content type="html">&lt;p&gt;I knew it was gonna happen, just knew it: I finally hit the first part of writing &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;local-first-from-scratch&#x2F;&quot;&gt;the book&lt;&#x2F;a&gt; where I have to make a choice between something being easy to describe and understand up front vs long-term efficiency or flexibility.&lt;&#x2F;p&gt;
&lt;p&gt;Let me explain.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;When you have a state-based CRDT, you have a &lt;code&gt;merge&lt;&#x2F;code&gt; operation. The details don&#x27;t &lt;em&gt;really&lt;&#x2F;em&gt; matter for this explanation, but basically if &lt;code&gt;merge&lt;&#x2F;code&gt; is commutative, idempotent, and associative, you can make use it for a CRDT and syncing state is as simple as:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Exchanging states with a peer.&lt;&#x2F;li&gt;
&lt;li&gt;Both sides call &lt;code&gt;merge&lt;&#x2F;code&gt; to get the final state.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;With a few more supporting details, that&#x27;s easy to understand and explain! (Even easier because in the book the only peer is a sync server.)&lt;&#x2F;p&gt;
&lt;p&gt;However this sync protocol gives up efficiency for that simplicity: you have to send all the data back and forth over the network. &lt;em&gt;Most&lt;&#x2F;em&gt; of the time, you&#x27;re gonna be sending a bunch of duplicate data with a few changes, which means you&#x27;re actually sending all the data &lt;em&gt;twice&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;You can improve efficiency by sending only the things that changed: if I&#x27;ve got a grow-only set, and I add &lt;code&gt;1&lt;&#x2F;code&gt; to it, I only send &lt;code&gt;1&lt;&#x2F;code&gt; to my peer. In fact, you can keep track of all the tiny pieces of data as separate instances of the CRDT, and only send the ones you care about. For example, the set &lt;code&gt;{1, 2, 3}&lt;&#x2F;code&gt; and the sets &lt;code&gt;{1}&lt;&#x2F;code&gt;, &lt;code&gt;{2}&lt;&#x2F;code&gt;, and &lt;code&gt;{3}&lt;&#x2F;code&gt; (when merged) represent the same data.&lt;&#x2F;p&gt;
&lt;p&gt;This implies that &lt;code&gt;merge&lt;&#x2F;code&gt; can be inverted. We&#x27;ll call that operation &lt;code&gt;split&lt;&#x2F;code&gt;. Like &lt;code&gt;merge&lt;&#x2F;code&gt;, it needs to have a couple of properties:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;split&lt;&#x2F;code&gt; should produce irreducible CRDTs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;merge&lt;&#x2F;code&gt;ing the results of &lt;code&gt;split&lt;&#x2F;code&gt; should give you the original state.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The literature I&#x27;ve read claims that you can &lt;code&gt;split&lt;&#x2F;code&gt; all CRDTs, and I haven&#x27;t found any counterexamples yet!&lt;&#x2F;p&gt;
&lt;p&gt;That lets us be even more efficient. Our sync protocol can look like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Keep track of the changes you make but haven&#x27;t sent to each peer, either as a single instance of the CRDT or as a list of &lt;code&gt;split&lt;&#x2F;code&gt; results.&lt;&#x2F;li&gt;
&lt;li&gt;Send only those changes during sync. (But &lt;code&gt;merge&lt;&#x2F;code&gt; them before sending, because that usually reduces the total byte size.)&lt;&#x2F;li&gt;
&lt;li&gt;When you receive changes, &lt;code&gt;split&lt;&#x2F;code&gt; what you get and remove any parts that you already knew about.&lt;&#x2F;li&gt;
&lt;li&gt;Add any new parts to the list of changes you send on to other peers (but not the peer you got them from.)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;vitorenes.org&#x2F;post&#x2F;2019&#x2F;04&#x2F;efficient-sync&#x2F;&quot;&gt;Vitor Enes et al have a paper and blog post on this sync protocol if you want more details&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, it seems like these protocols would be pretty easy to build up to (since &lt;code&gt;split&lt;&#x2F;code&gt; is just exposing a specific way of thinking about &lt;code&gt;merge&lt;&#x2F;code&gt;.) I&#x27;m having a hard time, though, because I need a way to store the CRDT bits earlier than I want to put the syncing details. Although, I suppose I could:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Store the whole replica state as a JSON blob locally.&lt;&#x2F;li&gt;
&lt;li&gt;Introduce the sync server and introduce &lt;code&gt;split&lt;&#x2F;code&gt; to deal with storage in Postgres there.&lt;&#x2F;li&gt;
&lt;li&gt;Implement the first sync protocol as a full state sync.&lt;&#x2F;li&gt;
&lt;li&gt;Improve to delta-state sync from there.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Might work, might not. I guess we&#x27;ll see!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Local-First From Scratch, part 1</title>
		<published>2024-12-17T00:00:00+00:00</published>
		<updated>2024-12-17T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/lffs-001/" type="text/html"/>
		<id>https://bytes.zone/micro/lffs-001/</id>
		<content type="html">&lt;p&gt;Since around Thanksgiving, I&#x27;ve been working on the draft of a book I&#x27;m tentatively calling &quot;Local-First from Scratch.&quot; The idea is basically to write &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;tinyping&#x2F;&quot;&gt;tinyping&lt;&#x2F;a&gt;, but do it in book form.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;One thing I had to decide right away was whether this would use some off-the-shell local-first library or if I was going to explain and implement everything from first principles. I chose the latter, which I think will make for a more interesting read, and has a nice side benefit that it doesn&#x27;t tie the book to the libraries and frameworks that are popular (or exist!) today. Hopefully that will mean that it stays pretty evergreen!&lt;&#x2F;p&gt;
&lt;p&gt;This choice has also made me dive deeply into the theory behind CRDTs. I&#x27;ve found a lot of things that I just misunderstood on previous learning expeditions, just by following the little voice in my head that says &quot;hey… is that right, actually?&quot; I want to get things right so that I&#x27;m not misleading people, and that turns out to be a pretty powerful motivator for learning!&lt;&#x2F;p&gt;
&lt;p&gt;I hope that I can ship a first beta version to a small group of readers in the first couple of months of 2025. If you&#x27;re interested, please get in touch!&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a basic outline:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Introduction:&lt;&#x2F;strong&gt; a similar pitch to what I just wrote above, but less meta.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Foundations:&lt;&#x2F;strong&gt; what we&#x27;re building, why local-first, and what are CRDTs?&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Breaking Ground:&lt;&#x2F;strong&gt; Implementing some basic CRDTs (and logical clocks)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Building an App:&lt;&#x2F;strong&gt; Modeling the data in our app based on the building blocks we just implemented.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;First Client:&lt;&#x2F;strong&gt; Building a TUI to be able to use the app model we just made.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Synchronization:&lt;&#x2F;strong&gt; Building a server to synchronize data between replicas.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Second Client:&lt;&#x2F;strong&gt; Building a browser-based client using the same codebase and sync server, and extending the app model to support time reporting.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Sync Revisited:&lt;&#x2F;strong&gt; Making the sync protocol way more efficient by only sending the parts of the data that have changed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Conclusion:&lt;&#x2F;strong&gt; What we&#x27;ve learned, what we could do next, and where to go from here.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Appendix, Sequence CRDTs:&lt;&#x2F;strong&gt; Extending our CRDT building blocks to support sequences.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Local-First From Scratch</title>
		<published>2024-11-29T00:00:00+00:00</published>
		<updated>2025-12-04T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/local-first-from-scratch/" type="text/html"/>
		<id>https://bytes.zone/projects/local-first-from-scratch/</id>
		<content type="html">&lt;p&gt;I wanted to write a book about local-first software. Instead of doing a survey of all the libraries available, I took a first principles approach. That meant doing a lot of explaining CRDTs, syncing, and the like, but I had a ton of fun with it.&lt;&#x2F;p&gt;
&lt;p&gt;As of March 2025, I had the introduction and first chapter done, and was working on the implementation that would end up being built up throughout the book.&lt;&#x2F;p&gt;
&lt;p&gt;I already had one false start where I made a time tracker. This turned out to not cover enough ground to be useful: when I tried to make a usergroup presentation on CRDTs using what I had done already, I found pretty big gaps. That meant a shift in approach. If you&#x27;re interested in where I got, you can get the WIP software at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bytes-zone&#x2F;beeps&quot;&gt;bytes-zone&#x2F;beeps&lt;&#x2F;a&gt; on GitHub.&lt;&#x2F;p&gt;
&lt;p&gt;My interest level in this project fell of over time as a I realized how in-flux the local-first ecosystem was. Someone could totally write a book on it! It just couldn&#x27;t be me at that time.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>CRDTs for tinyping</title>
		<published>2024-10-07T00:00:00+00:00</published>
		<updated>2024-10-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/crdts-for-tinyping/" type="text/html"/>
		<id>https://bytes.zone/micro/crdts-for-tinyping/</id>
		<content type="html">&lt;p&gt;When I&#x27;ve been thinking about sync for tinyping, I&#x27;ve been using off-the-shelf CRDTs (mostly Automerge.) But thinking about it from very simple principles, I don&#x27;t need all that. Tinyping can be broken down to:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;A log of timestamps&lt;&#x2F;li&gt;
&lt;li&gt;Tags and other data for those timestamps&lt;&#x2F;li&gt;
&lt;li&gt;A few settings (like the lambda value for calculating the offsets.)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I think we can sync that data with:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;A grow-only set for the timestamps (you never delete them, and they&#x27;re implicitly sortable)&lt;&#x2F;li&gt;
&lt;li&gt;Last-write-wins registers for timestamps and extra data.&lt;&#x2F;li&gt;
&lt;li&gt;Last-write-wins registers for settings&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;That depends on being able to reliably say what the &quot;last&quot; write is, of course. I think a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jaredforsyth.com&#x2F;posts&#x2F;hybrid-logical-clocks&#x2F;&quot;&gt;hybrid logical clock&lt;&#x2F;a&gt; might be the right approach here.&lt;&#x2F;p&gt;
&lt;p&gt;The only thing I&#x27;m not really sure about is how to do syncing. Do we keep the latest clock of each node that we&#x27;ve seen on each device and then ask each other for operations newer than that? That seems like it&#x27;d be pretty straightforward, but I don&#x27;t know if there are things I&#x27;m not considering here.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I&#x27;m going to give it a try and report back!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>doing in a day what I could not in 6 months</title>
		<published>2024-10-04T00:00:00+00:00</published>
		<updated>2024-10-04T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/doing-in-a-day-what-i-could-not-in-six-months/" type="text/html"/>
		<id>https://bytes.zone/micro/doing-in-a-day-what-i-could-not-in-six-months/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been working on tinyping for quite a while now, in various forms. Six or seven months, in fact. I feel frustrated that I don&#x27;t have anything usable to show for it. When I stopped doing thing-a-month, &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;stopping-thing-a-month&#x2F;&quot;&gt;I wrote&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;it turns out I&#x27;m pretty good at planning projects like this, but when I have such limited free time it&#x27;s way too easy to overcommit.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That&#x27;s still true! And unfortunately, removing the constraint of a month did not help. I have spent more time figuring out the local-first ecosystem than actually building something useful, and it&#x27;s really starting to annoy me.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So I wonder: what do the ideas in tinyping look like I have to build them in an hour? A day? What does a different version of extreme constraint look like?&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;So I&#x27;m going to try that. A successful solution should:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Notify someone that a new ping is ready to answer (but not tell them when the next one will be coming.)&lt;&#x2F;li&gt;
&lt;li&gt;Let them assign a single tag to a ping (and optionally additional information like energy level)&lt;&#x2F;li&gt;
&lt;li&gt;Make sure the distribution of pings is correct (&lt;code&gt;math.log(random.random()) &#x2F; lambda * -1&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Ideally, this should all happen in a stable way (e.g. reliably setting the next ping with PCR instead of using system randomness) but it&#x27;s fine if it doesn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;Just to prove I could do it quickly, I spent a little time (maybe two, three hours?) making a barebones version in Rust that just does the core part of the collection loop, nothing more. It lives at https:&#x2F;&#x2F;github.com&#x2F;bytes-zone&#x2F;beeps. You can download the binaries and try it, if you like (it always tries to invoke &lt;code&gt;say&lt;&#x2F;code&gt;, so the Windows and Linux binaries are a slight lie; get in touch if it matters to you.)&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;having a project in mind to use for learning new tools is fine, but I&#x27;ve spent more time trying to figure out ways around the constraints of the tools I&#x27;ve chosen than I have actually building. It feels like an artificial hindrance that I could just opt out of, and that I might be better off just doing so.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>you can try tinyping, if you want</title>
		<published>2024-09-23T00:00:00+00:00</published>
		<updated>2024-09-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/you-can-try-tinyping-if-you-want/" type="text/html"/>
		<id>https://bytes.zone/micro/you-can-try-tinyping-if-you-want/</id>
		<content type="html">&lt;p&gt;Despite not talking about it much, I&#x27;m continuing to work on tinyping. In the spirit of working with the garage door up, you can try an &lt;em&gt;extremely early version&lt;&#x2F;em&gt; at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;app.tinyping.net&quot;&gt;app.tinyping.net&lt;&#x2F;a&gt;. It mostly works, though! Pings are scheduled and stored appropriately, and you can edit tags for them. It also follows the Automerge project&#x27;s advice on how to best structure documents to avoid the main document growing and growing forever (it stores pings in journal documents, one per month.)&lt;&#x2F;p&gt;
&lt;p&gt;If you decide to try that, be aware that it doesn&#x27;t gracefully handle schema changes at all, in cases where the data storage format has to change. I&#x27;m mostly just calling &lt;code&gt;localStorage.clear()&lt;&#x2F;code&gt; whenever in the console whenever I need. Don&#x27;t put anything valuable in there; it&#x27;ll get eaten.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Next step there is to make the UI actually nice. I don&#x27;t think that will be awful to do, although the list of pings can feel a little overwhelming if you haven&#x27;t filled them in for a while.&lt;&#x2F;p&gt;
&lt;p&gt;Oh, and if you&#x27;re reading this and have a better idea for a name, please get in touch. I wanted to get the .com version of the name, but the owners of that domain want &quot;in the 5 figures of GBP&quot; and I&#x27;m not gonna do that for a side project.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>platform engineers work on the meta-product</title>
		<published>2024-09-23T00:00:00+00:00</published>
		<updated>2024-09-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/platform-engineers-work-on-the-meta-product/" type="text/html"/>
		<id>https://bytes.zone/posts/platform-engineers-work-on-the-meta-product/</id>
		<content type="html">&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;what-is-platform-engineering&#x2F;&quot;&gt;what is platform engineering?&lt;&#x2F;a&gt;, I gave this definition:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Platform engineering, as a discipline, takes a coherent approach to improving an engineering organization&#x27;s output in ways that the rest of the business can see and understand.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I&#x27;ve had some thoughts and discussions since then, though, and I think that definition needs revising. Specifically, I tried to be very clear in the last post that I didn&#x27;t think platform engineering could&#x2F;should be gatekept. Problem is, that previous definition has some scoping issues—mostly around vagueness—that could work against that goal.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;For one thing, it gives a bad-faith interpretation some gotchas: you could be doing the work of platform engineering, but have someone argue that your approach is not &quot;coherent&quot; or that the business cannot see and understand your work. I gave examples in the last post, but that feels like a bad substitute for the definition itself being clearer.&lt;&#x2F;p&gt;
&lt;p&gt;For another, the language here is too broad: there are differences between SRE, devops, and platform engineering, but this definition does not give you the tools to distinguish between them. To be fair, I think this is minor; this definition gives enough information to get started, or to explain what you&#x27;re doing to someone outside your immediate work. But I also think definitions should allow for clear distinctions, and this one doesn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, I think I see a way around all these problems in two steps. First: who is a platform engineer?&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A platform engineer is someone who does the work of platform engineering.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;If you make art, you&#x27;re an artist. If you make music, you&#x27;re a musician. If you do the work of platform engineering, you&#x27;re a platform engineer. That definition resists gatekeeping, which I like. But what is the work?&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Platform engineers work on the internal product that other teams use to make the customer-facing product.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Or more succinctly:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Platform engineers work on the meta-product.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I like a couple things about this. First, this clearly includes the things we care about: CI&#x2F;CD, developer experience, tooling, moving observability tools to where developers can see and use them, etc. If it’s part of the internal product, then it&#x27;s in scope.&lt;&#x2F;p&gt;
&lt;p&gt;Second, it distinguishes between the work of platform engineering and SREs or devops. For example, when I&#x27;ve worked in devops or SRE roles, I&#x27;ve cared a lot about what applications are running where, what kind of traffic loads we&#x27;re expecting, or how much we&#x27;re spending to deliver the product. These are all customer-facing concerns, and might not be in scope for platform engineering.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; The meta-product here would be working on things like adopting Terraform or Kubernetes, which automates a lot of that work and allows teams to ship the product more efficiently.&lt;&#x2F;p&gt;
&lt;p&gt;Now, someone might say &quot;ah, but SREs care a lot about automation! That&#x27;s the meta-product and actually a big part of the gig!&quot; Yes, good, that&#x27;s correct! But check out the new definition: are they working on the meta-product? Yes? Then they&#x27;re doing platform engineering in addition to SREing. That&#x27;s fine; I acknowledge the overlap here. Part of the utility of this definition is that it allows us to recognize this overlap, while still making a distinction. People hired into platform engineering roles may (and probably will) work with SREs on cloud automation projects, but that&#x27;s not their only thing.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s examine this from the dev side as well: at a small company, you probably don&#x27;t have someone specifically doing platform engineering as their whole role (for, say, more than 50% of their time.) But you still need a platform: gotta set up CI and a deployment target, at minimum. Congratulations, you&#x27;re doing platform engineering! Nobody can tell you you’re not.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, in the platform engineering role itself, this serves as a good charter for an individual or team. Where there’s overlap, you can share the load. Where there’s not, it’s clearly your responsibility.&lt;&#x2F;p&gt;
&lt;p&gt;So there you have it: a better definition. This still isn’t perfect, but I think it’s a big improvement over the last one.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;I know about (and have worked on) teams named &quot;platform engineering&quot; who spend most of their time taking care of cloud infrastructure. I think that&#x27;s more a titling issue than anything, and it depends more on the age of the organization than their level of seriousness. Terms vary, people use language differently, it&#x27;s fine.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>what is platform engineering?</title>
		<published>2024-09-09T00:00:00+00:00</published>
		<updated>2024-09-09T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/what-is-platform-engineering/" type="text/html"/>
		<id>https://bytes.zone/posts/what-is-platform-engineering/</id>
		<content type="html">&lt;p&gt;Today I&#x27;m starting my third&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#jobnum&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; platform engineering job. During the interview process, I had a few people ask me what exactly a platform engineer does, and I realized that…&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;I&#x27;ve had my own short answer—&quot;a platform engineer finds and reduces friction in the engineering organization&quot;—but that&#x27;s more an example than a proper definition, plus it doesn&#x27;t cover everything.&lt;&#x2F;li&gt;
&lt;li&gt;There are a bunch of confusing&#x2F;contradictory answers floating around the internet if you just search it. This is made worse by vendors who seem to want to gatekeep the term to sell products. (Can you really even &lt;em&gt;be&lt;&#x2F;em&gt; a platform engineer if you&#x27;re not running an IDP???&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#idp&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Contact our sales team!)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Anyway, I though it&#x27;d be useful to try and sum up the things that platform engineering is&#x2F;does in a succinct definition. &lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt; Here&#x27;s what I came up with. I&#x27;m not saying it&#x27;s perfect, but it&#x27;s helped me understand my role better:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Platform engineering, as a discipline, takes a coherent approach to improving an engineering organization&#x27;s output in ways that the rest of the business can see and understand.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Breaking some of that down:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;an coherent approach:&lt;&#x2F;strong&gt; The thing that sets platform engineering apart is that it focuses on making an integrated whole out of processes that are often otherwise approached in a piecemeal (or incoherent) way. (You could also say &quot;holistic&quot; or &quot;integrative&quot; or &quot;product-minded approach.&quot;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;improving an engineering organization&#x27;s output:&lt;&#x2F;strong&gt; By focusing on output, platform engineering takes a fairly wide area of responsibility. Improvement can mean a bunch of different things, as can output.
&lt;ul&gt;
&lt;li&gt;Some examples: improving p99 response times, increasing test coverage, decreasing CI pipeline runtime, coordinating penetration tests, consolidating docs, finding new project management tools, auditing and classifying runtime exceptions, and training teams in new tools and methodologies. (As a matter of fact, I&#x27;ve done all of those at some point or another as a platform engineer.)&lt;&#x2F;li&gt;
&lt;li&gt;As a counterexample, platform engineers typically do not work on the &quot;people&quot; side of the engineering organization. For example, we don&#x27;t train managers or consult with teams on the best ways to work together. But we &lt;em&gt;do&lt;&#x2F;em&gt; conduct ourselves with the human side in mind.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;in ways the rest of the business can see and understand:&lt;&#x2F;strong&gt; to be effective, platform engineers need to be able to talk about our work in ways that the rest of the business can buy into. For example, this might mean being able to talk about both the system&#x27;s ability to handle increases in transaction volume and what that means for scaling runway. Usually this means quantifying achievements, but it also may mean being able to talk about, say, the impacts of developer experience on voluntary attrition.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To put it another way, platform engineers work &lt;em&gt;on&lt;&#x2F;em&gt; the engineering organization instead of just &lt;em&gt;in&lt;&#x2F;em&gt; it, at least to some degree. They can&#x27;t spend 100% of their time here though: platform engineering needs to have a solid idea of the day-to-day challenges and pains the organization experiences in order to do an effective job.&lt;&#x2F;p&gt;
&lt;p&gt;This also isn&#x27;t work that can be gate-kept, no matter how much a vendor might want to. You need to do at least some of this work to have a functioning software business in the first place. For example, how does your code make it to production?&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#devops&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; How are builds and tests run? Engineers need to do this work regardless of if they have someone specifically focused on platform engineering, but it might be at the cost of not focusing on the things that they excel at.&lt;&#x2F;p&gt;
&lt;p&gt;You sometimes hear people talking about this with the terms &quot;inner loop&quot; vs &quot;outer loop.&quot; Time spent on the primary process of improving the product (coding, writing tests, etc) is the inner loop, and time spent on stuff like deployments or requirement definition is the outer loop. The value in hiring someone to do platform engineering for your business is that they can work on minimizing the time everybody else has to spend in the outer loop.&lt;&#x2F;p&gt;
&lt;p&gt;There are a couple other interesting things that fall out of this definition too. For example, it explains why platform engineering tends to be the interface between product engineering and security or QA (because they&#x27;re responsible for output and coherence) and why platform engineers may be later hires at companies (because smaller companies may have more visibility into cross-departmental processes.)&lt;&#x2F;p&gt;
&lt;p&gt;At any rate, I found this definition helpful and I hope you do too!&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;jobnum&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Third-ish, anyway. Maybe fourth, as the eldest of those was a devops-ish role focused entirely on building a deployable PaaS on top of Apache Mesos. At any rate, at least two of the four jobs predate the term &quot;platform engineering&quot; as we know it now by a couple of years. The principles here have been relevant in all those positions, though!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;idp&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Integrated developer platform, basically an integrated portal to an organization&#x27;s docs, best practices, deployment patterns, service catalog, etc.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;devops&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;This hints at the fact that platform engineers, devops, and SREs have very closely related responsibilities. I&#x27;d say the defining difference is what work each would consider &quot;the inner loop.&quot; DevOps and SREs I&#x27;ve worked with have been primarily concerned with safely delivering and running software while platform engineers I&#x27;ve worked with tend to focus more on developer productivity and &quot;shift left&quot; work. This has not been globally true, though.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>platform engineer at PayNearMe</title>
		<published>2024-09-09T00:00:00+00:00</published>
		<updated>2025-11-19T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/platform-engineer-at-paynearme/" type="text/html"/>
		<id>https://bytes.zone/projects/platform-engineer-at-paynearme/</id>
		<content type="html">&lt;p&gt;In September 2024, I started at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;paynearme.com&quot;&gt;PayNearMe&lt;&#x2F;a&gt; as a platform engineer (architect level.) I wrote some early thoughts during the interview process in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;what-is-platform-engineering&#x2F;&quot;&gt;what is platform engineering?&lt;&#x2F;a&gt; and refined them in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;platform-engineers-work-on-the-meta-product&#x2F;&quot;&gt;platform engineers work on the meta-product&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Notes from setting up Nomad</title>
		<published>2024-06-17T00:00:00+00:00</published>
		<updated>2024-06-17T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/notes-from-setting-up-nomad/" type="text/html"/>
		<id>https://bytes.zone/micro/notes-from-setting-up-nomad/</id>
		<content type="html">&lt;p&gt;A couple people have asked me for my impressions of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nomadproject.io&quot;&gt;Nomad&lt;&#x2F;a&gt;. I used it a little bit back before 1.0, but haven&#x27;t touched it for ~8 years so I&#x27;m going in with relatively fresh eyes.&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;stack&quot; here is Vault, Consul, and Nomad. Here&#x27;s a snippet of what they do:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Vault: secrets management. As well as basic key&#x2F;value stuff, it can provision things like database credentials on demand.&lt;&#x2F;li&gt;
&lt;li&gt;Consul: service discovery and configuration store.&lt;&#x2F;li&gt;
&lt;li&gt;Nomad: container scheduling. Basically, give it declarative manifests of apps to run and it&#x27;ll figure out placements. Pretty advanced in terms of scheduling; it can do blue&#x2F;green by default, plus any constraints you&#x27;d like (e.g. keeping services from being all on the same node&#x2F;rack, or running one instance of a service on every node.) Not just containers either; it can do raw exec, Java, firecracker VMs, etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;I tried to get as much as possible configured declaratively (either with Nix or Terraform) and I think I&#x27;ve mostly succeeded, although secrets management needs a little bit of cleanup (maybe with agenix?)&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a collection of impressions in no particular order:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Overall, I really like using this system. Everything feels well-put-together. It&#x27;s a neater garden than my Kubernetes setup was.&lt;&#x2F;li&gt;
&lt;li&gt;Probably the biggest thing to figure out was which piece of the stack depended on which other pieces. You can set it up however you like: for example, Nomad can use Consul to discover clients&#x2F;servers for clustering, or you can run Consul &lt;em&gt;using&lt;&#x2F;em&gt; Nomad. I ended up setting up all three in server mode on a control box, then adding a few clients to run workloads (although it could hypothetically happen all on one machine.) Nomad depends on Consul service discovery to connect clients to the server, and on Vault for OIDC login.&lt;&#x2F;li&gt;
&lt;li&gt;I would have appreciated more guidance on the &quot;right way&quot; to set up TLS. Nomad relies on mutual TLS to keep unauthorized clients from joining the cluster as well as the usual transport security. There&#x27;s a CLI to set it up with a self-signed CA, but the docs all make vague references to getting better certificates somehow. I&#x27;m not a security person, so that feels just a little unsettling!&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;m not 100% clear what the network security model in Nomad is. For example, I thought I could use Consul Connect (a service mesh) to control access to services within the cluster, but when I used it I found that I could still connect to all my services in the private Hetzner network. I&#x27;d like to restrict that eventually, but I gave up for now since the Envoy sidecars from Connect are pretty heavy given my tasks&#x27; needs.&lt;&#x2F;li&gt;
&lt;li&gt;I really love how declarative and complete the Nomad job specs are. To get similar in Kubernetes, I&#x27;d need multiple deployments with pod definitions plus services and HPAs, probably running something like Argo Rollouts to get blue&#x2F;green deployments. In Nomad that&#x27;s one file and one control loop.&lt;&#x2F;li&gt;
&lt;li&gt;I also love the integrations between products in the stack. For example, there&#x27;s a Nomad provider for Terraform, and I&#x27;m doing CD for all my workloads by templating out Nomad jobspecs using Terraform variables.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The one thing that makes me feel annoyed about this setup is the somewhat arbitrary line that gets drawn between the &quot;community&quot; (free) and enterprise versions of all the software. For example, you can set up OIDC login for Nomad in the community edition, but that&#x27;s a paid feature in Consul.&lt;&#x2F;p&gt;
&lt;p&gt;There are also a few features that I&#x27;d be willing to pay a reasonable small-business-sized fee to access, but not a &quot;contact us for enterprise pricing&quot; fee. The biggest one is resource quotas: I can set up namespacing within Nomad, but all namespaces can run whatever they&#x27;d like with no limit. I&#x27;d like to restrict that, just to limit the blast radius if I mess something up or my continuous delivery API keys get leaked and some cryptominer decides to set up shop in my cluster. I &lt;em&gt;kinda&lt;&#x2F;em&gt; get why it&#x27;s an enterprise feature (since it essentially segments Nomad into per-team resource pools) but I&#x27;d still like it.&lt;&#x2F;p&gt;
&lt;p&gt;Other than that, I have very few drawbacks about using Nomad. The BSL&#x2F;IBM stuff coming up is the biggest thing, but I think I&#x27;ve got a good amount of time before post-acquisition shenanigans would make me want to switch.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Kubernetes whoops</title>
		<published>2024-06-10T00:00:00+00:00</published>
		<updated>2024-06-10T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/kubernetes-whoops/" type="text/html"/>
		<id>https://bytes.zone/micro/kubernetes-whoops/</id>
		<content type="html">&lt;p&gt;Welp, I accidentally deleted my Kubernetes cluster. I was messing around with Hetzner Cloud and Nomad trying to figure out if they&#x27;d be a good fit for what I&#x27;m doing and ran a &lt;code&gt;terraform delete&lt;&#x2F;code&gt; thinking it would just affect my Hetzner machines. Turns out there&#x27;s a good reason that the &lt;code&gt;delete&lt;&#x2F;code&gt; subcommand is disabled by default in Terraform Cloud.&lt;&#x2F;p&gt;
&lt;p&gt;It took me a bit to come back, but this blog and all my sites are now running on Nomad. I&#x27;m pretty happy with it. May switch back eventually (it&#x27;s easy because everything is static and containerized) but not immediately.&lt;&#x2F;p&gt;
&lt;p&gt;As a result, no progress on tinyping these past couple of weeks. 😅&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>tinyping&#x27;s stack</title>
		<published>2024-05-28T00:00:00+00:00</published>
		<updated>2024-05-28T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/tinypings-stack/" type="text/html"/>
		<id>https://bytes.zone/micro/tinypings-stack/</id>
		<content type="html">&lt;p&gt;Hey! It&#x27;s been a couple weeks. The big piece of progress is on tinyping: I&#x27;ve gotten unstuck by making different technical choices. Instead of using Elm for this, I&#x27;m using XState, Automerge, Tailwind CSS, and Svelte. This has forced me to learn some new things, which was a byproduct I wanted! Here are my impressions of each:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;xstate.js.org&quot;&gt;XState&lt;&#x2F;a&gt;:&lt;&#x2F;strong&gt; I&#x27;m using version 5, which moved focus from state charts to an actor model. I&#x27;m fine with that; I love both of those models. I&#x27;ve been having a little trouble pinning down exactly what I want the models to be in XState, but they feel pretty solid once I get there. &lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;automerge.org&quot;&gt;Automerge&lt;&#x2F;a&gt;:&lt;&#x2F;strong&gt; Works fine. Stores data, syncs data. Only two problems: first, it&#x27;s not clear how to do schema migrations. There was some work on that in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;inkandswitch&#x2F;cambria-automerge&quot;&gt;Cambria&lt;&#x2F;a&gt; but it seems stalled. The bigger problem is that the core logic of the library is distributed as a WASM blob, and it&#x27;s 1.8 megabytes. OOF. I love the ideas around this but I might switch to a different library just to get away from enormous bundled assets like that. (To be fair, the Automerge team is aware of this and it&#x27;s being worked on.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tailwindcss.com&#x2F;&quot;&gt;Tailwind&lt;&#x2F;a&gt;:&lt;&#x2F;strong&gt; I&#x27;ve seen people raving about this, and finally decided to give it a try. It&#x27;s… weird. I&#x27;m having to look up a bunch of new ways to do things I&#x27;m used to, and it makes my markup feel very visually noisy. The resulting CSS is reasonably-sized, though. I grabbed &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tailwindui.com&#x2F;&quot;&gt;Tailwind UI&lt;&#x2F;a&gt; to get access to well-written components to copy in and learn from.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;svelte.dev&#x2F;&quot;&gt;Svelte&lt;&#x2F;a&gt;:&lt;&#x2F;strong&gt; I&#x27;m just using Svelte (not SvelteKit) and it&#x27;s been… fine, I guess. I&#x27;ve hit a number of places where I expected a value to be subscribed and then it wasn&#x27;t, so I thought that logic was not working when it was actually the UI just stalled. It seems like the upcoming Svelte 5 might have a better way to do this with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;svelte.dev&#x2F;blog&#x2F;runes&quot;&gt;runes&lt;&#x2F;a&gt;. It might make sense for me to switch to React, though: I don&#x27;t need a lot of routing for this app but I&#x27;d like to eventually have an app running on phones, which looks like it might be tricky with Svelte.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Anyway, I&#x27;ve been enjoying working on this and have also had a few ideas about how to solve the onboarding questions I wrote about a couple weeks ago. I&#x27;ll see about fleshing those out in writing as I get there!&lt;&#x2F;p&gt;
&lt;p&gt;As a little nice note: we reached the end of the school year and I&#x27;m looking forward to other people being in the house during the day again!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>tinyping and scrub moves</title>
		<published>2024-05-13T00:00:00+00:00</published>
		<updated>2024-05-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/tinyping-and-scrub-moves/" type="text/html"/>
		<id>https://bytes.zone/micro/tinyping-and-scrub-moves/</id>
		<content type="html">&lt;p&gt;I spent this week doing two things:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Working on design for tinyping&lt;&#x2F;li&gt;
&lt;li&gt;Wondering if I&#x27;m playing scrub games by developing this idea&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s take them in turn:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;design-for-tinyping&quot;&gt;Design for TinyPing&lt;&#x2F;h2&gt;
&lt;p&gt;As far as design is concerned, I think I have two basic jobs-to-be-done with the app: trying to discover how you&#x27;re spending your time, or trying to change the same. The design challenge comes in when you factor in that you can&#x27;t actually &lt;em&gt;do&lt;&#x2F;em&gt; those things until you&#x27;ve collected enough data, which takes at least a week of consistent data entry.&lt;&#x2F;p&gt;
&lt;p&gt;My basic idea to solve this is to show useful information as soon as I can—but that still might be too slow, so I might have to show un-useful information sooner. For this purpose:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Useful information&lt;&#x2F;strong&gt; means being able to say things like &quot;recently, you spent much more time on &lt;code&gt;meetings&lt;&#x2F;code&gt;. Do you want to dig into that?&quot;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Un-useful information&lt;&#x2F;strong&gt; might be more like &quot;you&#x27;ve entered 23 pings&quot;—there&#x27;s really no insight about how to live your life there, but we can present it right away.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I might also be able to show locked versions of the more useful report types on a dashboard so that it&#x27;s clear that entering more pings will unlock more stuff. I think that&#x27;d be pretty helpful, but I need to tune when it would be useful to unlock each report type!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;scrub-moves&quot;&gt;Scrub Moves&lt;&#x2F;h2&gt;
&lt;p&gt;The meta-thing here is that I&#x27;m wondering if I&#x27;m making a scrub move by spending my time on tinyping at all. I&#x27;m not talking about TLC&#x27;s version of a scrub here, but the one introduced in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;commoncog.com&#x2F;playing-to-play-playing-to-win&#x2F;&quot;&gt;&lt;em&gt;Are You Playing to Play, or Playing to Win?&lt;&#x2F;em&gt; by Cedric Chin&lt;&#x2F;a&gt;. Basically: a scrub plays a game according to their own stricter ruleset and complains about others winning in &quot;cheap&quot; ways that don&#x27;t fit their ideal.&lt;&#x2F;p&gt;
&lt;p&gt;The specific thing that gives me doubt here is that tinyping, as an idea, is more useful to individuals instead of businesses. That might make it difficult for it to make enough income to justify ongoing development: in general, individuals are less willing to pay and churn more easily, especially if their financial situation changes. This is well-known and shows up often on lists of beginner business mistakes you should avoid—but here I am working on something that falls exactly in that category!&lt;&#x2F;p&gt;
&lt;p&gt;The thing that could move this from &quot;beginner mistake&quot; to &quot;scrub move&quot; is that I&#x27;ve been justifying continuing to work on it &quot;because I want it to exist.&quot; That&#x27;s fine if &lt;em&gt;if it works&lt;&#x2F;em&gt;, but if tinyping immediately has high costs to run or is not useful at all, then I&#x27;m wasting a lot of time building it.&lt;&#x2F;p&gt;
&lt;p&gt;Then why am I continuing? Because I think there are two ways to avoid being a scrub despite the appearance of scrubbiness:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;improve at the game despite your restricted rules&lt;&#x2F;li&gt;
&lt;li&gt;don&#x27;t complain if you lose&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;My hedge against the first is that I&#x27;m working on transferable skills while building tinyping. For example, &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;elm-duet&#x2F;&quot;&gt;elm-duet&lt;&#x2F;a&gt; is useful for projects outside this one. I&#x27;m also thinking hard about how to design a tricky interaction, which I hope leads to some wisdom about how to solve cart-before-the-horse product design problems in general.&lt;&#x2F;p&gt;
&lt;p&gt;Against the second, I suppose I&#x27;ve just got to have a good attitude, or not worry too much if tinyping doesn&#x27;t take off as a product. (Although that&#x27;s not far off from &quot;commit less&quot; which could also sink it!)&lt;&#x2F;p&gt;
&lt;p&gt;At any rate, I&#x27;m going to continue down the path of trying to make this work this week. It&#x27;s likely to be another low-code week because I&#x27;ll be working on design, but I&#x27;d rather spend a couple weeks on planning then waste them on implementing the wrong things!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>tinyping</title>
		<published>2024-05-13T00:00:00+00:00</published>
		<updated>2024-05-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/tinyping/" type="text/html"/>
		<id>https://bytes.zone/projects/tinyping/</id>
		<content type="html">&lt;p&gt;Tinyping is a tool to help people (myself included) to make sure they&#x27;re using their time in accordance with their values.&lt;&#x2F;p&gt;
&lt;p&gt;It does this by random sampling: every so often, it will ask you what you&#x27;re working on.
Once it has enough pings, you can use statistics to build up an accurate picture of how you&#x27;re using your time!&lt;&#x2F;p&gt;
&lt;p&gt;Tinyping is currently under development and hasn&#x27;t been released yet; there&#x27;s a placeholder page at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tinyping.net&quot;&gt;tinyping.net&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This project started out as &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month-awareness&#x2F;&quot;&gt;the last thing-a-month&lt;&#x2F;a&gt;, and was inspired by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doc.beeminder.com&#x2F;tagtime&quot;&gt;TagTime&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elm-duet 0.1.0</title>
		<published>2024-05-07T00:00:00+00:00</published>
		<updated>2024-05-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/elm-duet-0-1-0/" type="text/html"/>
		<id>https://bytes.zone/micro/elm-duet-0-1-0/</id>
		<content type="html">&lt;p&gt;Just a quick note: I just made &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;elm-duet&quot;&gt;elm-duet&lt;&#x2F;a&gt; public and released v0.1.0.
If it sounded great to you based on &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-04-02&#x2F;&quot;&gt;my post from the other day&lt;&#x2F;a&gt; then you can now get it!
(But if you remember that, it&#x27;s also worth noting I improved those docs a lot!)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Hopefully&lt;&#x2F;em&gt; this represents a stable release, but we&#x27;ll see what we see when other people get their hands on it.
I&#x27;m certainly planning to work on tinyping with this as the base, so I&#x27;m sure there will be changes and an 0.2.0 down the road a ways.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Stopping thing-a-month</title>
		<published>2024-05-06T00:00:00+00:00</published>
		<updated>2024-05-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/stopping-thing-a-month/" type="text/html"/>
		<id>https://bytes.zone/micro/stopping-thing-a-month/</id>
		<content type="html">&lt;p&gt;I&#x27;m going to have to stop doing thing-a-month. I really love it as an idea, but it&#x27;s not working out so well for me:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;it turns out I&#x27;m pretty good at planning projects like this, but when I have such limited free time it&#x27;s way too easy to overcommit.&lt;&#x2F;li&gt;
&lt;li&gt;trying to figure out how to build a business around a tiny idea in a month is a bit much.&lt;&#x2F;li&gt;
&lt;li&gt;doing that &lt;em&gt;and writing about it&lt;&#x2F;em&gt; is even more work.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Taken together, it basically means I&#x27;m overcommitting, then having to be accountable for it in public. Not a great combination for me mentally; I&#x27;m avoiding building things that I want and that I think sound fun to make!&lt;&#x2F;p&gt;
&lt;p&gt;So something&#x27;s gotta change.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Instead, I think I&#x27;m going to drop the thing-a-month commitment and instead commit to writing weekly updates here. I&#x27;ve noticed it&#x27;s better for me to be able to commit to steady progress than big epic effort. (That&#x27;s probably true of a lot of people, but I think we&#x27;re conditioned by storytelling to think that the epic efforts are the better choice.)&lt;&#x2F;p&gt;
&lt;p&gt;Anyway.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what I&#x27;ve been working on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I really love the idea of tinyping and I&#x27;d like it to exist in some form in the world.
&lt;ul&gt;
&lt;li&gt;As a reminder, there are a few apps that do this already (see a list at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doc.beeminder.com&#x2F;tagtime&quot;&gt;Beeminder&#x27;s page about this&lt;&#x2F;a&gt;) but none of them sync the data anywhere, making it harder to get pings during the time when you&#x27;re doing what you&#x27;re doing. In the interests of making the data entry easier, I&#x27;m trying to solve this problem by using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;automerge.org&#x2F;&quot;&gt;automerge&lt;&#x2F;a&gt; as the storage layer (which also makes it local-first software!)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Because of some trouble I had keeping types straight between Elm and TypeScript (as well as similar pains I had at work) I started making something I&#x27;m calling elm-duet. It accepts an interop schema using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jsontypedef.com&#x2F;&quot;&gt;JSON Type Definitions&lt;&#x2F;a&gt; and generates both the Elm and TypeScript code. I spent most of April working on this, and it&#x27;s almost ready for release. It has some ergonomic issues in the generated code, but it&#x27;s definitely a step up already.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The plan going into this week is to (re)write the README for elm-duet and get that released. It&#x27;s not the most polished piece of software yet, but I think it&#x27;s worth getting out there as an idea. We&#x27;ll see where we end up next Monday!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elm-duet</title>
		<published>2024-04-23T00:00:00+00:00</published>
		<updated>2024-04-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-04-02/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-04-02/</id>
		<content type="html">&lt;p&gt;Just a little update on tinyping: I have a system that mostly works! Yay! It doesn&#x27;t do reporting, but it&#x27;ll alert you about new pings correctly.&lt;&#x2F;p&gt;
&lt;p&gt;This actually doesn&#x27;t feel like a huge win to me because it&#x27;s so, so messy. I&#x27;m still using Automerge for this, and I think it&#x27;s a solid decision, but taking the distributed nature of the system into account while building this has been both tricky and instructive.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Anyway, while making what I have of tinyping so far, I kept running into issues with syncing information across the Elm&#x2F;TS boundary. It&#x27;s really annoying to have to update the types on both sides. This has bugged me for a while across multiple projects, even at work, so I decided to dive down the rabbit hole and try to fix this. Here&#x27;s a preview in the form of the current README of the project:&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;elm-duet&quot;&gt;elm-duet&lt;&#x2F;h2&gt;
&lt;p&gt;Elm is great, and TypeScript is great, but the flags and ports between them are hard to use safely.
They&#x27;re the only part of the a system between those two languages that aren&#x27;t typed by default.&lt;&#x2F;p&gt;
&lt;p&gt;You can get around this in various ways, of course, either by maintaining a definitions by hand or generating one side from the other.
In general, though, you run into a couple different issues:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It&#x27;s easy for one side or the other to get out of date and errors to slip through CI and code review into production.&lt;&#x2F;li&gt;
&lt;li&gt;Definitions in one language may not be translatable to the other (despite the two type systems having pretty good overlap.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;elm-duet&lt;&#x2F;code&gt; tries to get around this by creating a single source of truth to generate both TypeScript definitions and Elm types with decoders.
We use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jsontypedef.com&#x2F;&quot;&gt;JSON Type Definitions&lt;&#x2F;a&gt; (JTD, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jsontypedef.com&#x2F;docs&#x2F;jtd-in-5-minutes&#x2F;&quot;&gt;five-minute tutorial&lt;&#x2F;a&gt;) to say precisely what we want and generate ergonomic types on both sides (plus helpers like encoders to make testing easy!)&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example for an app that stores a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jwt.io&#x2F;&quot;&gt;jwt&lt;&#x2F;a&gt; in &lt;code&gt;localStorage&lt;&#x2F;code&gt; or similar to present to Elm:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;modules&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;Main&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;flags&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        &amp;quot;properties&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;          &amp;quot;currentJwt&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            &amp;quot;type&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;string&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            &amp;quot;nullable&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;ports&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        &amp;quot;newJwt&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;          &amp;quot;metadata&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            &amp;quot;direction&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;ElmToJs&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;          &amp;quot;type&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;string&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        &amp;quot;logout&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;          &amp;quot;metadata&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            &amp;quot;direction&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;ElmToJs&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can generate code from this by calling &lt;code&gt;elm-duet path&#x2F;to&#x2F;your&#x2F;schema.json&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ elm-duet examples&#x2F;jwt_schema.json --typescript-dest examples&#x2F;jwt_schema.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wrote examples&#x2F;jwt_schema.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which results in this schema:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; Warning: this file is automatically generated. Don&amp;#39;t edit by hand!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;declare module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Elm&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  namespace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Main&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Flags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;      currentJwt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Ports&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;      logout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;        subscribe&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;callback&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Record&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; never&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; void&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; void&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;      newJwt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;        subscribe&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;callback&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; void&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; void&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; init&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; flags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Flags&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; HTMLElement&lt;&#x2F;span&gt;&lt;span&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; void&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(Elm code generation is currently TODO.)&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the full help to give you an idea of what you can do with the tool:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ elm-duet --help&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Generate Elm and TypeScript types from a single shared definition.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Usage: elm-duet [OPTIONS] &amp;lt;SOURCE&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Arguments:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;SOURCE&amp;gt;  Location of the definition file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Options:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      --typescript-dest &amp;lt;TYPESCRIPT_DEST&amp;gt;  Destination for TypeScript types [default: elm.ts]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      --elm-dest &amp;lt;ELM_DEST&amp;gt;                Destination for Elm types [default: src&#x2F;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      --elm-prefix &amp;lt;ELM_PREFIX&amp;gt;            Prefix for Elm module path [default: Interop]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -h, --help                               Print help&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -V, --version                            Print version&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>tinyping month 2</title>
		<published>2024-04-01T00:00:00+00:00</published>
		<updated>2024-04-01T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-04-01/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-04-01/</id>
		<content type="html">&lt;p&gt;Let&#x27;s talk about tinyping. The thing-a-month project is meant to be a thing a month, full stop. But sometimes life gets in the way: between illness and a long-scheduled and much-needed vacation, I didn&#x27;t have the time I needed to do a good job on tinyping in March.&lt;&#x2F;p&gt;
&lt;p&gt;But I really think it&#x27;s a good idea, and not such a big one. It&#x27;s also pretty close to being usable! So this month I&#x27;m going to double down on it, breaking my own rules about the &quot;things&quot; only taking a month.&lt;&#x2F;p&gt;
&lt;p&gt;So here&#x27;s a quick status report, and where I hope to get to in April:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ul&gt;
&lt;li&gt;Ping calculation can be distributed among multiple clients in a reliable way, which should keep the right distribution from the two posts in early March (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-04-01&#x2F;@.&#x2F;thing-a-month-03-01.md&quot;&gt;1&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-04-01&#x2F;@.&#x2F;thing-a-month-03-02.md&quot;&gt;2&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Style is... really not there yet.&lt;&#x2F;li&gt;
&lt;li&gt;Neither is reporting.&lt;&#x2F;li&gt;
&lt;li&gt;You also can&#x27;t sync data anywhere, which is especially bad on mobile (and &lt;em&gt;especially&lt;&#x2F;em&gt; on mobile Safari, which will delete stuff in IndexedDB after only 7 days of inactivity if you don&#x27;t pin the app to the home screen.)&lt;&#x2F;li&gt;
&lt;li&gt;Nothing is deployed anywhere.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So here are the goals for April:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Get reporting working. This is a core thing—there&#x27;s not much point entering the data if we can&#x27;t get the results.&lt;&#x2F;li&gt;
&lt;li&gt;Get notifications on new pings. This is almost done, and just needs a little push to get it over the line.&lt;&#x2F;li&gt;
&lt;li&gt;Get some semblance of style. It&#x27;s &lt;em&gt;really&lt;&#x2F;em&gt; rough right now, and nothing I&#x27;d want to show someone else.&lt;&#x2F;li&gt;
&lt;li&gt;Get syncing to a server, at least for backup. I might have to drop encryption to get this done in April; that&#x27;s OK.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;And the things I&#x27;m &lt;em&gt;not&lt;&#x2F;em&gt; going to do (unless I miraculously get the above done):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple tags per ping&lt;&#x2F;li&gt;
&lt;li&gt;Bulk editing&lt;&#x2F;li&gt;
&lt;li&gt;Integration into other systems&lt;&#x2F;li&gt;
&lt;li&gt;Any kind of native or mobile app&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;My work&#x27;s cut out for me; let&#x27;s go!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Kratky in the basement</title>
		<published>2024-04-01T00:00:00+00:00</published>
		<updated>2024-04-01T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/kratky-in-the-basement/" type="text/html"/>
		<id>https://bytes.zone/posts/kratky-in-the-basement/</id>
		<content type="html">&lt;p&gt;&lt;em&gt;Hello hello! Today is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.aprilcools.club&#x2F;&quot;&gt;April Cools&lt;&#x2F;a&gt;, which is like April Fools but for posting stuff outside what you normally write about instead of unfunny jokes. Hope you enjoy!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I want to eat more salads, but:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Viral and bacterial outbreaks come up pretty regularly in the US food system. I don&#x27;t feel super paranoid about produce where you can wash or avoid eating the skin, but I feel worse in cases where usually you eat the whole plant.&lt;&#x2F;li&gt;
&lt;li&gt;Large-scale farming techniques sometimes seem pretty dubious, even in products labeled as organic.&lt;&#x2F;li&gt;
&lt;li&gt;Because of how our food system is set up, produce is regularly transported hundreds of miles before being consumed.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Luckily for me, doing hydroponic gardening at home in my basement is a way around this:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ol&gt;
&lt;li&gt;I won&#x27;t get listeria cross-contamination from pig farms or whatever—there are no pigs in my basement!&lt;&#x2F;li&gt;
&lt;li&gt;Growing indoors means I have precise control over the environment the plants are growing in. There is no need for pesticide or herbicide. (And I have lots of choice in what nutrients I use.)&lt;&#x2F;li&gt;
&lt;li&gt;I can walk downstairs to harvest lettuce which avoids all the waste and carbon emissions from transportation, plus the produce could literally not be fresher.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Plus I think hydroponics are cool,&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; which is another helpful motivation!&lt;&#x2F;p&gt;
&lt;p&gt;Problem is, I&#x27;ve always overcomplicated things… for example, I&#x27;ve drawn up plans to turn the entire house into a hydroponic garden, feed the whole neighborhood, start a local plastic recycling and hydroponic produce market, etc. You get the idea.&lt;&#x2F;p&gt;
&lt;p&gt;So earlier this year, I decided to commit to doing something as small as I could: get at least one salad on a plate before the end of March. And I did it! Here&#x27;s how.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hydroponics-crash-course&quot;&gt;Hydroponics Crash Course&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m still a beginner and I&#x27;ll get some of this wrong for sure, but before I can explain how my setup works I need to share a little theory. Plants need like 4 things&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; to thrive:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Water&lt;&#x2F;li&gt;
&lt;li&gt;Bioavailable nutrients&lt;&#x2F;li&gt;
&lt;li&gt;Light&lt;&#x2F;li&gt;
&lt;li&gt;Air&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;First up, water and nutrients are taken together in hydroponics. Mixing the right hydroponic nutrient solution could be a long post all by itself, so I&#x27;ll just sum it up by sharing some details that I was confused by at first:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Nutrient formulations are marked with three-number value like 10-10-10 or 8-15-36. Those values are nitrogen-phosphorous-potassium, or NPK. As I understand it, they&#x27;re percentages: a 10-10-10 mix has 10% of each. That&#x27;s a good starter mix for lettuce, but I&#x27;ve read that 8-15-36 will also work.&lt;&#x2F;li&gt;
&lt;li&gt;You measure pH and electrical conductivity in the nutrient solution.
&lt;ul&gt;
&lt;li&gt;You need to care about pH because if it&#x27;s too high or low then plants can&#x27;t actually absorb the nutrients from the water.&lt;&#x2F;li&gt;
&lt;li&gt;You need to care about electrical conductivity because it&#x27;s a good proxy for how much total nutrient is in the solution (since saltier water is more electrically conductive.) Too much and you&#x27;ll get &quot;nutrient burn&quot; where the edges of leaves turn brown. Too little and the plant just won&#x27;t grow correctly.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Light is usually a cost&#x2F;value tradeoff. Good grow lights are expensive, and smaller ones usually have a price premium due to home marijuana growers in the US market.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; I bought mine from &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;spider-farmer.com&#x2F;&quot;&gt;spider-farmer.com&lt;&#x2F;a&gt; (got a pair of SF300s for $99 on sale, $135 normal price.) There is not a lot to think about if you&#x27;re willing to accept this as the biggest cost of the initial setup, but the most important thing is that you have to have a full-spectrum light: plants grow best in reds and blues, basically (which makes sense if you think about it; leaves are green because they&#x27;re reflecting those wavelengths instead of absorbing them for photosynthesis.)&lt;&#x2F;p&gt;
&lt;p&gt;That leaves air, which different systems adapt to in different ways. The basic problem is that plants can drown if you submerge their roots in water that isn&#x27;t properly aerated. This is a problem in soil gardening too; it&#x27;s why you need to ensure good drainage in your planting beds. In most systems I was looking at, there was some level of circulation or aeration which required a bunch of pumps and air stones. But it turns out that you don&#x27;t need all that at a small scale: you can just use the Kratky method.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-kratky-method&quot;&gt;The Kratky Method&lt;&#x2F;h3&gt;
&lt;p&gt;So that all leads to Kratky: You basically take a bucket, make sure the plants can get their roots wet, and just let them grow. Plants will grow regular roots down into the reservoir, and send off air roots (they&#x27;re hairier and shorter than the ones that absorb water) sideways above the waterline as the water evaporates.&lt;&#x2F;p&gt;
&lt;p&gt;The big benefit is that you don&#x27;t need pumps, air stones, or any powered components aside from a light (and if you do it outside, you don&#x27;t even need that.) That kind of simplicity is what I was after!&lt;&#x2F;p&gt;
&lt;p&gt;The big drawback is that the nutrient concentration will go up over time as the water evaporates, so you need to start lower. For lettuce, I&#x27;ve read that starting at half-strength nutrient concentration is fine. You also will probably not be able to cycle new plants into the system, as their roots will not be able to reach the lower water levels.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-setup&quot;&gt;My Setup&lt;&#x2F;h2&gt;
&lt;p&gt;So here&#x27;s how I put all that theory into practice. As a reminder, I wanted to get a single salad out of a system that was as simple as I could possibly make it.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how things looked at the start of the grow. I only needed one light for six plants (which I calculated based on how much room a typical lettuce plant needs to grow.)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;kratky-in-the-basement&#x2F;kratky-27gal-20231226.jpeg&quot; alt=&quot;a photo of six seedling plants with 1 to 3 small leaves each in individual holes in the top of a yellow 27-gallon bin underneath a grow light&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what it looked like near the middle of the grow below the waterline. You can see the air roots coming off the sides of the longer roots!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;kratky-in-the-basement&#x2F;kratky-27gal-20220122-roots.jpeg&quot; alt=&quot;a photo of a tray of six plants being lifted out of the the nutrient solution to show their roots. Most of the roots are long and slender, but near the tops they look hairy: those are the air roots.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then finally, here&#x27;s how the plants looked near the end of the grow about 45 days in:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;kratky-in-the-basement&#x2F;kratky-27gal-20240219.jpeg&quot; alt=&quot;a photo of the same six plants underneath a grow light, but with large healthy leaves.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;They stayed at this stage for about another month while we used all the plants in salads. We did that slowly at first, because we weren&#x27;t 100% sure if they were actually safe to eat, but we sped up pretty quickly as we didn&#x27;t see any ill effects. I wish we had eaten them faster, though: the nutrient concentration went up in the reservoir and most of the plants ended up with nutrient burn that I had to trim off before eating.&lt;&#x2F;p&gt;
&lt;p&gt;The easiest way I found to harvest the plants was to cut them off at the stem with a sharp knife, then chop them the same as I would a grocery store lettuce. I plucked all the leaves off one as an experiment, though: it took me a lot longer, but then the plant was able to regrow most of its leaves for a second harvest. If it was easier to change the water in this system, I might do that more.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, here&#x27;s the shopping list if you wanted to replicate something like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;A reservoir. I used a 27 gallon (~100 liter) tote with holes drilled in the lid.&lt;&#x2F;li&gt;
&lt;li&gt;Net cups and grow media. I used some standard net cups off of amazon with some rockwool that I got from a local hydroponics shop.&lt;&#x2F;li&gt;
&lt;li&gt;Seeds.&lt;&#x2F;li&gt;
&lt;li&gt;Lights. I used Spider Farmer SF300s, which I plan to use for future grows as well. Link above.&lt;&#x2F;li&gt;
&lt;li&gt;Nutrient solution. I don&#x27;t have a strong recommendation here—I bought some vegetative growth nutrient mix from the hydroponics shop. It&#x27;s clearly designed for the vegetative phase of marijuana, but it has about the right NPK and it worked fine. I monitored the pH and conductivity for the first 30 days or so, but stopped once it stabilized.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I want to make some improvements already, though:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I&#x27;m going to switch to 5 gallon (18 liter) buckets for future grows. 25 gallons of water weighs about 200 pounds (~90kg), and the tote isn&#x27;t really set up for that much weight. Using small buckets would let me run different experiments with pH and nutrient composition as well.&lt;&#x2F;li&gt;
&lt;li&gt;I did the grow in my basement. That meant a pretty constant temperature of about 60°F (15.5°C.) It might be worth getting some kind of grow tent with a small heater to raise the temperature.&lt;&#x2F;li&gt;
&lt;li&gt;My hydroponics setups have a fan blowing on the plants to stimulate them in this kind of environment. I think I ought to try that!&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;m not sold on rockwool as a grow medium—I found out while writing this post that I have not been sufficiently careful about handling it! I have coco pellets too, but I&#x27;m not sure how to start seeds in that.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So that&#x27;s where I&#x27;m at on hydroponics! I&#x27;m really excited to improve on this, but I&#x27;m already pretty happy with where things are right now.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;If you don&#x27;t think hydroponics are cool, that&#x27;s fine. But if the other stuff in this list resonates with you, consider looking up local farmers markets or produce co-ops, which address some of these problems in similar ways.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Ok, so there&#x27;s actually a bunch more here. Plants need the right temperature, humidity, protection from insects or animals, a place to put down roots, etc. But you need to solve for these four to even get started.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;I call it the &quot;weed tax&quot;, although I can&#x27;t recall if I read that or came up with it.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elm-duet</title>
		<published>2024-04-01T00:00:00+00:00</published>
		<updated>2024-05-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/elm-duet/" type="text/html"/>
		<id>https://bytes.zone/projects/elm-duet/</id>
		<content type="html">&lt;p&gt;While building tinyping, I had a lot of trouble syncing complex types between Elm and TypeScript, so I started building elm-duet.
It takes a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jsontypedef.com&#x2F;&quot;&gt;JSON Type Definition&lt;&#x2F;a&gt; schema and generates both Elm and TypeScript type definitions so the system can be type-checked end-to-end.&lt;&#x2F;p&gt;
&lt;p&gt;You can get source and binary releases for many platforms at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;elm-duet&quot;&gt;BrianHicks&#x2F;elm-duet&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>seeing the forest through the trees</title>
		<published>2024-03-15T00:00:00+00:00</published>
		<updated>2024-03-15T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-03-04/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-03-04/</id>
		<content type="html">&lt;p&gt;I’ve been pretty heads down trying to get the first version of tinyping done this week. Here are the highlights:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ol&gt;
&lt;li&gt;Automerge is very cool, and it seems like it might even be able to do the encrypted syncing that I want. Data migrations are really tricky though, though.&lt;&#x2F;li&gt;
&lt;li&gt;I’m using Elm for this, since I’m super familiar with it, but the way I’m setting up the app right now means duplicating responsibility for data storage. Not great.&lt;&#x2F;li&gt;
&lt;li&gt;The hardest part has been figuring out how to schedule pings according to the right distribution when there is no central authority. I &lt;em&gt;think&lt;&#x2F;em&gt; I have a solution though: using the last ping’s timestamp as a random seed should allow all clients to converge on the same sequence of pings. (Assuming the lambda value does not change, anyway!)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Despite the difficulties, I’m starting to see the bigger picture of how the app will work. That feels pretty encouraging!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>app architecture</title>
		<published>2024-03-06T12:44:04-06:00</published>
		<updated>2024-03-06T12:44:04-06:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-03-03/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-03-03/</id>
		<content type="html">&lt;p&gt;So, a little news to start: I decided on a name for this project and bought a domain. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tinyping.net&quot;&gt;tinyping.net&lt;&#x2F;a&gt; is now receiving traffic (but just linking back to bytes.zone for the moment.) But today I want to write about the ideal architecture!&lt;&#x2F;p&gt;
&lt;p&gt;Part of the thing-a-month project is to pare down my ideas to the minimum that I can actually achieve in a month. I thought that was going to be easy on this project, but then I got to thinking about data governance and privacy balanced with long-term sustainability as a service. Let&#x27;s think about how someone might use this app:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;They sign up, get whatever app installed and authed, whatever.&lt;&#x2F;li&gt;
&lt;li&gt;They start answering pings&lt;&#x2F;li&gt;
&lt;li&gt;They look at reports on how they&#x27;re using their time&lt;&#x2F;li&gt;
&lt;li&gt;Repeat, hypothetically forever (but actually who knows how long)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Questions about privacy and sustainability come up before the beginning, and in between each of those steps. For example:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;What does sign up look like? Is it a website, an app, or some combination?&lt;&#x2F;li&gt;
&lt;li&gt;Where does ping data live? Who gets to read it? How is sensitive data (how people are spending their time) protected?&lt;&#x2F;li&gt;
&lt;li&gt;Who bears the cost for a constantly (but slowly) growing data set?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s work out a couple scenarios. First, what&#x27;s the answer if this is a purely-local app? Say it&#x27;s on your phone (just to make it available everywhere, since people keep their phones close.) The answers to these questions would be:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What does sign up look like?&lt;&#x2F;strong&gt; Purchasing an app from an app store.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Where does ping data live?&lt;&#x2F;strong&gt; Local storage, or perhaps in the OS&#x27; persistent storage (e.g. iCloud)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Who gets to read ping data?&lt;&#x2F;strong&gt; Only the person who enters it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;How is sensitive data protected?&lt;&#x2F;strong&gt; App sandboxes, I guess? Could also be encrypted at rest if that&#x27;s something we&#x27;re interested in. Only has to be readable when the app is open.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Who bears the cost for the storing the data set?&lt;&#x2F;strong&gt; The person who owns the device it&#x27;s stored on.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That doesn&#x27;t seem so bad, but this is where I hit some scope creep. I&#x27;d like this data to sync between my phone and computers somehow. I&#x27;m on my work laptop for most of the workday, on my phone in the evenings, and on my personal laptop when I&#x27;m doing things like blogging or working on projects like this. I want to track all that time!&lt;&#x2F;p&gt;
&lt;p&gt;One way to do this might be to consider making this a web app instead of a local-only app. Then the answers might look like this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What does sign up look like?&lt;&#x2F;strong&gt; Some SaaS signup tactic. Username&#x2F;password, OAuth, magic email links, passkeys, whatever.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Where does ping data live?&lt;&#x2F;strong&gt; In a database that I control.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Who gets to read ping data?&lt;&#x2F;strong&gt; The person who enters it, plus hypothetically me the system administrator, plus hypothetically other people due to bugs or misconfigurations.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;How is sensitive data protected?&lt;&#x2F;strong&gt; &quot;best practices.&quot; I kid, but it&#x27;s at least a little earnest… making sure data in the database is encrypted at rest, controlling access, things like that.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Who bears the cost for the storing the data set?&lt;&#x2F;strong&gt; I do! And because of that my costs go up and one-time purchases become less sustainable (since I&#x27;d have a constant stream of cost paired with an intermittent stream of revenue.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;New question: will this work on a plane?&lt;&#x2F;strong&gt; Not without buying wifi!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That seems worse on first read. It does have some clear benefits, though: there aren&#x27;t nearly as many gatekeepers for web apps, and I wouldn&#x27;t have to pay platform fees on sales (other than, say, Stripe&#x27;s normal fees.) But I have a hard time thinking this is the &lt;em&gt;best&lt;&#x2F;em&gt; way forward.&lt;&#x2F;p&gt;
&lt;p&gt;But what about a hybrid approach? Local-first software is looking pretty good these days. What if the client stored its own data and then could sync with a server to get the data everywhere? Seems like that could work alright. Then what if the tags were encrypted somehow and could only be decrypted by the user on their devices? Then…&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What does sign up look like?&lt;&#x2F;strong&gt; Still a SaaS signup tactic.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Where does ping data live?&lt;&#x2F;strong&gt; In a database that I control.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Who gets to read ping data?&lt;&#x2F;strong&gt; The person who enters it, plus hypothetically someone in the far future who can break the encryption.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;How is sensitive data protected?&lt;&#x2F;strong&gt; The data is encrypted everywhere but where the app is actually running.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Who bears the cost for the storing the data set?&lt;&#x2F;strong&gt; Joint responsibility. Each client would be responsible for hosting all of its data, plus I&#x27;d store a copy too to facilitate syncing.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Will this work on a plane?&lt;&#x2F;strong&gt; Sure would!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That makes me think that the way to get started here might be a local-first PWA. That&#x27;d allow you to get pings immediately, and then later be able to sync them with a server when that exists. That makes signup a little annoying (since all the data lives on your device anyway) so maybe the first version of this could just be an open thing? Or maybe that&#x27;s how I&#x27;d make a trial available… use this for a while on your own device, but if you want backups and sync then there&#x27;s a fee (seems like this works fine for Obsidian!)&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>what&#x27;s between two pings?</title>
		<published>2024-03-05T13:00:00-06:00</published>
		<updated>2024-03-05T13:00:00-06:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-03-02/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-03-02/</id>
		<content type="html">&lt;p&gt;I got to thinking about how pings work in this system (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-03-01&#x2F;&quot;&gt;last post&lt;&#x2F;a&gt; and realized an optimization. Right now I&#x27;m treating them as though they&#x27;re all the same size—that&#x27;s safe because of the law of large numbers, remember—but they&#x27;re not all the same size! Time varies between pings.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Say you have these pings with these tags:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Time&lt;&#x2F;th&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8:00&lt;&#x2F;td&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;9:00&lt;&#x2F;td&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;9:30&lt;&#x2F;td&gt;&lt;td&gt;coffee&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;10:30&lt;&#x2F;td&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Assuming we&#x27;re comfortable with this small sample as being representative of what you actually did, you can say with confidence that between 8:00 and 9:00 you were working. But sometime (vaguely) between 9:00 and 9:30 you transitioned to making coffee, and sometime (vaguely) between 9:30 and 10:30 you transitioned back to working.&lt;&#x2F;p&gt;
&lt;p&gt;If we treat every ping as equal, assuming λ is 1 hour, this will be reported as 3 hours (± 0.85 hours) working and one hour (± 0.85 hours) getting coffee. That&#x27;s pretty good—or at least enough to get a sense of how you&#x27;re spending your time.&lt;&#x2F;p&gt;
&lt;p&gt;But what if we take advantage of the fact that pings &lt;em&gt;aren&#x27;t&lt;&#x2F;em&gt; exactly hourly? We&#x27;d have to take care of the vagueness of when you transitioned. In the absence of other data, we might just take the time halfway between two pings as the transition time. So that means that our times look like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Time&lt;&#x2F;th&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;th&gt;Duration&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;8:00&lt;&#x2F;td&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td&gt;0:30 (halfway to 9)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;9:00&lt;&#x2F;td&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td&gt;0:45 (halfway back to 8 plus halfway to 9:30)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;9:30&lt;&#x2F;td&gt;&lt;td&gt;coffee&lt;&#x2F;td&gt;&lt;td&gt;0:45 (halfway back to 9:30 plus halfway to 10:30)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;10:30&lt;&#x2F;td&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td&gt;0:30 (halfway back to 9:30)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;This makes it look like we have less time, though: we now have 2.5 hours tracked instead of 4. This is probably not a problem in real life: we can take pings continuously and tag any that aren&#x27;t answered as &quot;afk.&quot; If we really need to, it&#x27;s probably safe to double the duration of the first and last ping, giving us a total of 3.5 hours in this sample.&lt;&#x2F;p&gt;
&lt;p&gt;But does it give us better insight into our life? Let&#x27;s see. Doing this by hand:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;th&gt;Ping as hour&lt;&#x2F;th&gt;&lt;th&gt;Ping as halfway between&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td&gt;3h ± 0.85h&lt;&#x2F;td&gt;&lt;td&gt;2.75h ± 0.82h&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;coffee&lt;&#x2F;td&gt;&lt;td&gt;1h ± 0.85h&lt;&#x2F;td&gt;&lt;td&gt;0.75h ± 0.82h&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;It feels weird to me that the error bar goes below zero for coffee now. I definitely didn&#x27;t spend &lt;em&gt;no&lt;&#x2F;em&gt; time on it, much less negative time. But let&#x27;s pretend that getting coffee took 15 minutes and the remainder of the 4 hours was spent working: both of these systems produce a perfectly acceptable answer to the question of &quot;where did my day go?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Given that, I think the first version of this system should assume that pings are &lt;code&gt;1 hour &#x2F; λ&lt;&#x2F;code&gt; or similar instead of trying to get fancy. The transformation is not &lt;em&gt;that&lt;&#x2F;em&gt; hard (I&#x27;ll attach a Python script below that can evaluate &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-03-01&#x2F;&quot;&gt;the same data I generated in the last post&lt;&#x2F;a&gt;) so it would hypothetically be feasible to change if it looked like there was a big advantage to doing so. Although I want to be careful to avoid giving precise-but-fuzzy numbers, though: sticking with a rougher-grained unit as a base unit probably makes a ton of sense for setting expectations… you wouldn&#x27;t want to bill a client on data from this system, for example!&lt;&#x2F;p&gt;
&lt;p&gt;All this talk of coffee has made me want some. brb.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#!&#x2F;usr&#x2F;bin&#x2F;env python3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; argparse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; collections&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; datetime&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; datetime, timedelta&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; math&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Ping&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __init__&lt;&#x2F;span&gt;&lt;span&gt;(self, at, tag, duration):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.at&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; at&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; tag&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.duration&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; duration&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;classmethod&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; from_json&lt;&#x2F;span&gt;&lt;span&gt;(cls, obj):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; cls&lt;&#x2F;span&gt;&lt;span&gt;(datetime.fromisoformat(obj[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;at&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]), obj[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;tag&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;], timedelta(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __repr__&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&amp;lt;Ping at=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{self&lt;&#x2F;span&gt;&lt;span&gt;.at.isoformat()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tag=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{repr&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.tag)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;, duration=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{str&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.duration)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(args):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pings&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [Ping.from_json(obj)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; obj&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; json.load(sys.stdin)]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (i, ping)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; enumerate&lt;&#x2F;span&gt;&lt;span&gt;(pings):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            continue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        before&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; pings[i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        halfway&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (ping.at&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; before.at)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ping.duration&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span&gt; halfway&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        before.duration&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span&gt; halfway&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    total_seconds&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; sum&lt;&#x2F;span&gt;&lt;span&gt;((ping.duration.total_seconds()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; pings))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;From &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;timedelta(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;seconds&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;total_seconds)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hours tracked...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    total_seconds_by_tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; collections.Counter()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; pings:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        total_seconds_by_tag[ping.tag]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span&gt; ping.duration.total_seconds()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (tag, tag_total)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; total_seconds_by_tag.most_common():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; tag_total&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; total_seconds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        other_ping_proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; proportion&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        sem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; math.sqrt(proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; other_ping_proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; len&lt;&#x2F;span&gt;&lt;span&gt;(pings))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        plus_minus&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; sem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; total_seconds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}\t{&lt;&#x2F;span&gt;&lt;span&gt;tag_total&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;60&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;60&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:.2f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hours&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;plus or minus &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;plus_minus&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;60&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;60&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:.2f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hours&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __name__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; argparse.ArgumentParser()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser.add_argument(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;-l&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;float&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    main(parser.parse_args())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>messing around with statistics</title>
		<published>2024-03-05T07:00:00-06:00</published>
		<updated>2024-03-05T07:00:00-06:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-03-01/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-03-01/</id>
		<content type="html">&lt;p&gt;The basic idea behind TagTime is:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Choose how often you want to be asked what you&#x27;re up to. This constant is called λ. Let&#x27;s assume that &lt;code&gt;λ = 1&lt;&#x2F;code&gt; for now, which will mean &quot;once per hour.&quot; If you wanted to be asked every 30 minutes (on average) you could set this to 2.&lt;&#x2F;li&gt;
&lt;li&gt;Schedule pings (instances of being asked what you&#x27;re doing) into the future following a Poisson distribution (which means the time between pings follows an exponential distribution with an average of λ.) &lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ul&gt;
&lt;li&gt;It seems like the way to do this might be to generate a random value between 0 and 1 and plug it in like so: &lt;code&gt;math.log(random.random()) &#x2F; lambda * -1&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Since you know the time between pings averages out to λ, you perform analysis as if each ping was worth λ time (so if it&#x27;s 1, and that means 1 per hour, that means each ping is worth one hour.)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;m going to try this out and see how it works, just to test my understanding of the math. I&#x27;m going to write a Python program simulating someone with a very simple and strict schedule. Specifically:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;What&lt;&#x2F;th&gt;&lt;th&gt;Start&lt;&#x2F;th&gt;&lt;th&gt;End&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Sleep&lt;&#x2F;td&gt;&lt;td&gt;10:00pm&lt;&#x2F;td&gt;&lt;td&gt;6:00am&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Morning Activities&lt;&#x2F;td&gt;&lt;td&gt;6am&lt;&#x2F;td&gt;&lt;td&gt;7:30am&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Commute&lt;&#x2F;td&gt;&lt;td&gt;7:30am&lt;&#x2F;td&gt;&lt;td&gt;8:00am&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Work&lt;&#x2F;td&gt;&lt;td&gt;8:00am&lt;&#x2F;td&gt;&lt;td&gt;12:00pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Lunch&lt;&#x2F;td&gt;&lt;td&gt;12:00pm&lt;&#x2F;td&gt;&lt;td&gt;1:00pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Work&lt;&#x2F;td&gt;&lt;td&gt;1:00pm&lt;&#x2F;td&gt;&lt;td&gt;2:30pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Afternoon Coffee&lt;&#x2F;td&gt;&lt;td&gt;2:30pm&lt;&#x2F;td&gt;&lt;td&gt;2:36pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Work&lt;&#x2F;td&gt;&lt;td&gt;2:36pm&lt;&#x2F;td&gt;&lt;td&gt;5:00pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Commute&lt;&#x2F;td&gt;&lt;td&gt;5:00pm&lt;&#x2F;td&gt;&lt;td&gt;5:30pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Evening Activities&lt;&#x2F;td&gt;&lt;td&gt;5:30pm&lt;&#x2F;td&gt;&lt;td&gt;10:00pm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Some points of interest:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Lots of the schedule doesn&#x27;t fall exactly on hour boundaries.&lt;&#x2F;li&gt;
&lt;li&gt;A few of the activities are shorter than an hour.&lt;&#x2F;li&gt;
&lt;li&gt;One activity is tiny! Making coffee every day takes 6 minutes exactly (one tenth of an hour.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;m doing that to see how much data this system actually needs to capture smaller changes in activity throughout the day.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a Python script that can generate pings according to this schedule:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#!&#x2F;usr&#x2F;bin&#x2F;env python3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; argparse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; datetime&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; datetime, timedelta&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; math&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; random&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span&gt;(ping):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (ping.hour, ping.minute)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;22&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; or&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 8 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;sleep&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 1.5 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;morning&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 0.5 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;commute&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 4 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;work&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;13&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 1 hour&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;lunch&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;13&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 1.5 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;work&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 36&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 0.1 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;coffee&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 36&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 2.4 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;work&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 0.5 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;commute&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;22&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; # 4.5 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;evening&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(args):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    entries&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; []&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    next_ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; datetime.now()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; next_ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; timedelta(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;days&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; args.days)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    while&lt;&#x2F;span&gt;&lt;span&gt; next_ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt; end:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        entries.append({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;at&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: next_ping.isoformat(),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;tag&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: tag(next_ping) })&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        next_gap&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; math.log(random.random())&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; args.l&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; * -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        next_ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span&gt; timedelta(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;hours&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; next_gap)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    json.dump(entries, sys.stdout,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; indent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __name__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; argparse.ArgumentParser()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser.add_argument(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;days&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser.add_argument(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;-l&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;float&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;1&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    main(parser.parse_args())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next we assume that each ping is &lt;code&gt;1 &#x2F; λ&lt;&#x2F;code&gt; hours and calculate both the total time for all the pings. That total time comes out pretty close to what I&#x27;d expect: for a week, I&#x27;d expect 168 pings if there&#x27;s an average of one an hour and I&#x27;m getting like 173, 141, 167, 148, etc.&lt;&#x2F;p&gt;
&lt;p&gt;We sum up the total time per tag, then find out the standard error from the mean for each tag to get a range. Here&#x27;s the Python code that slurps up the &lt;code&gt;stdout&lt;&#x2F;code&gt; of the last script:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#!&#x2F;usr&#x2F;bin&#x2F;env python3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; argparse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; collections&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; datetime&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; datetime&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; math&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Ping&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __init__&lt;&#x2F;span&gt;&lt;span&gt;(self, at, tag):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.at&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; at&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; tag&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;classmethod&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; from_json&lt;&#x2F;span&gt;&lt;span&gt;(cls, obj):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; cls&lt;&#x2F;span&gt;&lt;span&gt;(datetime.fromisoformat(obj[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;at&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]), obj[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;tag&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __repr__&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&amp;lt;Ping at=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{repr&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.at)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tag=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{repr&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.tag)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(args):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pings&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [Ping.from_json(obj)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; obj&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; json.load(sys.stdin)]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    total_hours&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; len&lt;&#x2F;span&gt;&lt;span&gt;(pings)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; args.l)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;From &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;total_hours&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hours tracked...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    total_hours_by_tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; collections.Counter()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; ping&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; pings:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        total_hours_by_tag[ping.tag]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; args.l&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (tag, tag_total)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; total_hours_by_tag.most_common():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; tag_total&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; total_hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        other_ping_proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; proportion&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        sem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; math.sqrt(proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; other_ping_proportion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; total_hours)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}\t{&lt;&#x2F;span&gt;&lt;span&gt;tag_total&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hours&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;plus or minus &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;sem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; total_hours&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:.2f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hours&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __name__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; argparse.ArgumentParser()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser.add_argument(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;-l&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;float&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    main(parser.parse_args())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That outputs things like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;From 158.0 hours tracked...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sleep	56.0 hours	plus or minus 6.01 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;work	45.0 hours	plus or minus 5.67 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;evening	26.0 hours	plus or minus 4.66 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;morning	12.0 hours	plus or minus 3.33 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;commute	10.0 hours	plus or minus 3.06 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lunch	9.0 hours	plus or minus 2.91 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;From 193.0 hours tracked...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sleep	67.0 hours	plus or minus 6.61 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;work	58.0 hours	plus or minus 6.37 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;evening	40.0 hours	plus or minus 5.63 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;morning	13.0 hours	plus or minus 3.48 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lunch	7.0 hours	plus or minus 2.60 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;commute	6.0 hours	plus or minus 2.41 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;coffee	2.0 hours	plus or minus 1.41 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These are reasonably accurate! For any 7-day period, here&#x27;s how the &quot;actual&quot; time compared to the statistics:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Actual&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sample 1&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sample 2&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;sleep&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;56h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 56 ± 6.01&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 67 ± 6.61&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;55.3h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ 45 ± 5.67&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 58 ± 6.37&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;evening&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;31.5h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ 26 ± 4.66&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ 40 ± 5.63&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;morning&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.5h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 12 ± 3.33&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 13 ± 3.48&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;commute&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 10 ± 3.06&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 6 ± 2.41&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;lunch&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 9 ± 2.91&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 7 ± 2.60&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;coffee&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ did not capture&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 2 ± 1.41&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;I put a ✅ when the range covers the actual value and an ❌ when it doesn&#x27;t. As you can see, these samples get in the ballpark but the ranges don&#x27;t always cover the actual values. However, these would definitely be good enough to get a sense of how you&#x27;re spending your life as a whole, so maybe it&#x27;s OK!&lt;&#x2F;p&gt;
&lt;p&gt;I wonder, though, if it gets more accurate if you sample more frequently. An average of a half hour seems like it&#x27;d get annoying (because of the exponential distribution, some pings would be very close together) but I wonder about 45 minutes. Let&#x27;s try. That&#x27;s a λ of 1⅓. Here&#x27;s the results:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Actual&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sample 1&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sample 2&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;sleep&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;56h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 60.75 ± 6.3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 61.5 ± 6.14&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;55.3h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 59.25 ± 6.26&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ 48.75 ± 5.81&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;evening&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;31.5h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 27 ± 4.78&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 29.25 ± 4.89&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;morning&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.5h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 10.5 ± 3.14&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ 4.5 ± 2.09&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;commute&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 6.75 ± 2.55&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 8.25 ± 2.8&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;lunch&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 9 ± 2.92&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 6.75 ± 2.54&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;coffee&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 1.5 ± 1.22&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;❌ did not capture&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;That seems about the same. The ranges don&#x27;t feel like they&#x27;re that much smaller to me. Maybe half an hour really would be better?&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Actual&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sample 1&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sample 2&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;sleep&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;56h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 54 ± 6.04&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 52.5 ± 5.98&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;work&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;55.3h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 52 ± 5.98&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 49.5 ± 5.89&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;evening&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;31.5h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 33.5 ± 5.17&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 34 ± 5.2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;morning&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.5h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 10 ± 3.07&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 14 ± 3.58&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;commute&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 7 ± 2.59&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 7 ± 2.59&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;lunch&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 8 ± 2.76&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 7 ± 2.59&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;coffee&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.7h&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 1.5 ± 1.22&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;✅ 1 ± 1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Those two samples happen to be all green, but some of them &lt;em&gt;barely&lt;&#x2F;em&gt; squeaked in. I still think a λ of 30 minutes would be far too annoying, so I&#x27;m going to leave it out.&lt;&#x2F;p&gt;
&lt;p&gt;Next I&#x27;m going to go and see if this is the same stuff that the Perl version of TagTime &lt;em&gt;actually&lt;&#x2F;em&gt; uses, and then maybe repeat this analysis!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>wrapping up month 0</title>
		<published>2024-03-04T00:00:00-06:00</published>
		<updated>2024-03-04T00:00:00-06:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-09/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-09/</id>
		<content type="html">&lt;p&gt;Well, it&#x27;s March 4 already, so it&#x27;s about time to wrap up the first month with a short post. What&#x27;d I get done this month? Well:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I have a Kubernetes cluster ready to run all the workloads from my other experiments.&lt;&#x2F;li&gt;
&lt;li&gt;That cluster is running my blog and associated services, plus some other smaller sites.&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;ve got deployments all automated and ready to go.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;There is some work left undone, though, of course:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The cluster isn&#x27;t very well hardened yet. I&#x27;m pretty close there, though: I really only need to set up an alerting system for policy violations and security events and it&#x27;ll be done.&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;m still running &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&quot;&gt;git.bytes.zone&lt;&#x2F;a&gt; on the old infrastructure. I&#x27;m giving the few folks who are still using it some time to move their repos off.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Overall, I feel pretty good about this. I&#x27;ve learned a lot and I have something nice to show for it! On to next month!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>thing-a-month (awareness)</title>
		<published>2024-03-04T00:00:00+00:00</published>
		<updated>2024-05-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/thing-a-month-awareness/" type="text/html"/>
		<id>https://bytes.zone/projects/thing-a-month-awareness/</id>
		<content type="html">&lt;p&gt;The second &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month&#x2F;&quot;&gt;thing-a-month&lt;&#x2F;a&gt; project is a &quot;random time tracker&quot; I&#x27;m calling tinyping.&lt;&#x2F;p&gt;
&lt;p&gt;It works a bit like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doc.beeminder.com&#x2F;tagtime&quot;&gt;TagTime&lt;&#x2F;a&gt; and is good for getting a better awareness of moods and how I (and others) spend our time.&lt;&#x2F;p&gt;
&lt;p&gt;There are two big challenges here for me:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Understanding enough of the statistics to get useful results.
I think I&#x27;ve already go an OK understanding, but this will test that assumption.
However, it looks like the underlying math is actually fairly simple! (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-03-01&#x2F;&quot;&gt;1&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-03-02&#x2F;&quot;&gt;2&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Building something that can ask for discreet updates in enough places to be useful.
This probably means making a native app of some kind eventually to use the right notifications APIs.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I started this in March and &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-04-01&#x2F;&quot;&gt;continued into April&lt;&#x2F;a&gt; with the goal of getting something usable by the end of the month.
That didn&#x27;t quite happen, and I dropped thing-a-month at the end of that month.
Tinyping &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;tinyping&#x2F;&quot;&gt;lives on&lt;&#x2F;a&gt;, but not as a thing-a-month project.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>sourcing secrets from 1Password</title>
		<published>2024-02-27T00:00:00+00:00</published>
		<updated>2024-02-27T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/sourcing-secrets-from-opw/" type="text/html"/>
		<id>https://bytes.zone/posts/sourcing-secrets-from-opw/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month-meta&#x2F;&quot;&gt;setting up a personal Kubernetes cluster&lt;&#x2F;a&gt; recently.
The whole thing is set up with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;argoproj.github.io&#x2F;&quot;&gt;Argo CD&lt;&#x2F;a&gt;, which basically means that I make changes to the cluster by committing to a git repo and pushing.
Really handy, especially when I make a mistake and need to clean something up or revert.&lt;&#x2F;p&gt;
&lt;p&gt;One problem with this, though, is that it&#x27;s probably not a great idea to commit secrets to the repository.
I don&#x27;t want things like passwords and API keys sitting there in clear text on my laptop or on the remote.&lt;&#x2F;p&gt;
&lt;p&gt;If I were setting up a cluster that other people were going to operate, I&#x27;d probably set up something like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.vaultproject.io&#x2F;&quot;&gt;Vault&lt;&#x2F;a&gt;, but since it&#x27;s just me I can get away with creating the secrets by hand.
That said, I still don&#x27;t want to leave them on disk unencrypted!&lt;&#x2F;p&gt;
&lt;p&gt;Enter the &lt;code&gt;opw&lt;&#x2F;code&gt; CLI for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;1password.com&#x2F;&quot;&gt;1Password&lt;&#x2F;a&gt;. &lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
It can output secret data as JSON, and I can use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jqlang.github.io&#x2F;jq&#x2F;&quot;&gt;&lt;code&gt;jq&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to transform that into Kubernetes secret objects, which I can pipe into &lt;code&gt;kubectl&lt;&#x2F;code&gt; to apply in the cluster.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s take a simple example: an image pull secret.
I have the secret stored in 1Password under a specific vault.
If I run &lt;code&gt;op item get --vault k8s &quot;image pull secret&quot;&lt;&#x2F;code&gt;, I&#x27;ll get something like the following:&lt;&#x2F;p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;ID&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;          y63hsk7qy5osug5n2qupwhtz3e&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Title&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;       image pull secret&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Vault&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;       k8s (2qaqjigknr3htch273i2mho5vi)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Created&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;     2 weeks ago&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Updated&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;     2 weeks ago by Brian Hicks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Favorite&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Version&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;     1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Category&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    LOGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;Fields&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  password&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    password-goes-here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  username&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    username-goes-here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I can also ask for it in JSON format by adding &lt;code&gt;--format json&lt;&#x2F;code&gt; to the end:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;y63hsk7qy5osug5n2qupwhtz3e&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;title&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;image pull secreet&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;version&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;vault&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;2qaqjigknr3htch273i2mho5vi&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;k8s&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;category&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;LOGIN&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;last_edited_by&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;B76OR7I6DNENPAQOQFFO6FKQOM&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;created_at&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;2024-02-12T18:37:38Z&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;updated_at&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;2024-02-12T18:37:38Z&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;additional_information&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;brian@brianthicks.com&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;fields&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;password&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;type&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;CONCEALED&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;purpose&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;PASSWORD&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;label&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;password&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;value&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;password-goes-here&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;reference&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;op:&#x2F;&#x2F;k8s&#x2F;image pull secret&#x2F;password&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;password_details&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        &amp;quot;strength&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;FANTASTIC&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;username&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;type&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;STRING&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;purpose&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;USERNAME&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;label&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;username&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;value&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;username-goes-here&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;reference&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;op:&#x2F;&#x2F;k8s&#x2F;image pull secret&#x2F;username&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;notesPlain&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;type&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;STRING&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;purpose&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;NOTES&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;label&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;notesPlain&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;reference&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;op:&#x2F;&#x2F;k8s&#x2F;image pull secret&#x2F;notesPlain&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can then pipe that into the following jq program to get an object indexed by the field names:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.fields | map({ key: .label, value: .value }) | from_entries&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That produces this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;password&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;password-goes-here&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;username&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;username-goes-here&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  &amp;quot;notesPlain&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; null&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And we can add onto that jq program to produce some JSON in the shape of a Kubernetes secret:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.fields | map({ key: .label, value: .value }) | from_entries | {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    apiVersion: &amp;quot;v1&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    kind: &amp;quot;Secret&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    type: &amp;quot;kubernetes.io&#x2F;dockerconfigjson&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    metadata: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        name: &amp;quot;ghcr-image-pull-secret&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    data: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;quot;.dockerconfigjson&amp;quot;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            auths: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                &amp;quot;image-host.io&amp;quot;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    auth: &amp;quot;\(.username):\(.password)&amp;quot; | @base64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        } | @base64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Assuming that&#x27;s in &lt;code&gt;image-pull-secret.jq&lt;&#x2F;code&gt;, we can now load the secret into Kubernetes like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;op&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; item get&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --vault&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; k8s &amp;quot;image pull secret&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --format&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    jq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; image-pull-secret.jq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    kubectl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; apply&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; whatever-namespace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; -&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Like everything, this has some tradeoffs: the secrets are stored securely and never hit the disk, which is &lt;em&gt;great&lt;&#x2F;em&gt;!
But in exchange, I have to run a command manually if I want to update the secrets.
This means that if I&#x27;m creating a new app, it&#x27;ll probably fail at least once.
Same if I have to move my cluster (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-02-05&#x2F;&quot;&gt;which I already did once&lt;&#x2F;a&gt;) and have to apply all the manifests from scratch.&lt;&#x2F;p&gt;
&lt;p&gt;So in the end, would I recommend this?
Yes, if you&#x27;re in my situation!
But if you&#x27;re running a cluster where other people have to operate it too, this is probably not the best approach.
It&#x27;s good to know it&#x27;s possible, though!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>running this blog on Kubernetes</title>
		<published>2024-02-23T00:00:00+00:00</published>
		<updated>2024-02-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-08/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-08/</id>
		<content type="html">&lt;p&gt;This week has been wild but I&#x27;ve got an update: I&#x27;m now That Person who has a static site blog running on a Kubernetes cluster. 😆&lt;&#x2F;p&gt;
&lt;p&gt;The workflow for publishing a new post is now:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Write the post&lt;&#x2F;li&gt;
&lt;li&gt;Put it in a PR&lt;&#x2F;li&gt;
&lt;li&gt;Merge the PR when checks pass&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Everything else is automated. Once the PR merges, GitHub Actions builds the container with nixbuild.net, then pushes it to a container registry and updates the manifests file that Argo CD is listening to. Once Argo sees that, it updates the containers running in the cluster.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s quite a rube goldberg machine, but I&#x27;m happy with it. Now to get the rest of the little services I have running under bytes.zone ported!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Ukulele seems nice</title>
		<published>2024-02-23T00:00:00+00:00</published>
		<updated>2024-02-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/ukulele/" type="text/html"/>
		<id>https://bytes.zone/micro/ukulele/</id>
		<content type="html">&lt;p&gt;I&#x27;ve wanted to pick up an instrument again for a while now. I considered the guitar, but:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;the strings hurt my fingers&lt;&#x2F;li&gt;
&lt;li&gt;they&#x27;re so big!&lt;&#x2F;li&gt;
&lt;li&gt;when I&#x27;ve tried to learn, I&#x27;ve felt a lot of pressure to get good or get out. Rock star mentality, maybe? Not my speed, anyway.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;So instead, I&#x27;m going to try the ukulele:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ol&gt;
&lt;li&gt;nylon strings&lt;&#x2F;li&gt;
&lt;li&gt;small and cute&lt;&#x2F;li&gt;
&lt;li&gt;all the stuff I have found about ukulele is like &quot;yeah, we&#x27;re all here to have fun.&quot; Really chill, which I appreciate.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;And as a nice bonus I really like the sound of the ones I bought. I got two because my son wants to learn with me! I&#x27;m really looking forward to messing around and making some music!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>getting back off the CI train</title>
		<published>2024-02-20T00:00:00+00:00</published>
		<updated>2024-02-20T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-07/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-07/</id>
		<content type="html">&lt;p&gt;I got Woodpecker working, but the first job I did (a &lt;code&gt;nix build&lt;&#x2F;code&gt;) totally froze up the whole cluster for like an hour and a half, and didn&#x27;t even complete successfully. Pretty yikes. Looking at this realistically, I don&#x27;t want to buy the size nodes that I would need to do this properly, so it probably makes sense for me to use a hosted service (probably just a free one!) If I had a bunch of money to throw at this problem, though, I&#x27;d probably use Woodpecker. It was pretty nice!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;After doing some investigation, it looks like I&#x27;m going to end up on GitHub actions for now. I don&#x27;t &lt;em&gt;love&lt;&#x2F;em&gt; that—part of the reason I started self-hosting my own stuff was to get away from GitHub, but the price is right and it enables me to get going on things that I care about more than fiddling with CI runners.&lt;&#x2F;p&gt;
&lt;p&gt;Tradeoffs, as always: if I don&#x27;t want to run my own thing, I have to put up with what someone else is willing to run. That seems annoying, but fine.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>getting on the CI train</title>
		<published>2024-02-19T00:00:00+00:00</published>
		<updated>2024-02-19T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-06/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-06/</id>
		<content type="html">&lt;p&gt;If I’m going to be shipping apps by the end of the month, I’d better get CI set up. I’ve been looking around at different options and found:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ul&gt;
&lt;li&gt;Argo Workflows. Probably a taste that goes great with Argo CD, but I’d like to source CI steps from the repos and this seems difficult.&lt;&#x2F;li&gt;
&lt;li&gt;Tekton: similar.&lt;&#x2F;li&gt;
&lt;li&gt;Woodpecker: an offshoot of Drone, looks nice, seems to do what I want, but I can’t get the Helm chart working.&lt;&#x2F;li&gt;
&lt;li&gt;Agola: smaller, maybe newer. Does the normal CI stuff, plus you can define your config in jsonnet. Kind of cool. I’d like to be able to pull in editable build steps though.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Basically I feel these options are either too complex or too simple or I can’t make them run. I bet if I redid how I’m setting up apps in Argo CD, I could get Woodpecker working. May have to shave that yak!&lt;&#x2F;p&gt;
&lt;p&gt;Right now the plan is to try a little longer (maybe today and Tuesday) to get Woodpecker working, and to bail and use Agola if I can&#x27;t.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>moving Kubernetes to New Jersey</title>
		<published>2024-02-15T00:00:00+00:00</published>
		<updated>2024-02-15T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-05/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-05/</id>
		<content type="html">&lt;p&gt;So this morning was shaving yaks. (I mean, what infrastructure project isn’t?) I got up thinking I’d spend a couple hours before work setting up Keycloak, but then I realized that it needs a Postgres database for a proper setup, so I comparison shopped Postgres operators. But in testing out my choice, I realized that the region I deployed the cluster into doesn’t have support for NVMe drives. No good for databases!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;So I ended up tearing down the cluster and moving everything to a different region (New Jersey instead of Chicago.) I was very pleased that the gitops stuff really shone here. I only needed to inject a few secrets manually and everything else came back online by itself.&lt;&#x2F;p&gt;
&lt;p&gt;This would have been much harder if I had to think about uptime or data migration, so I’m glad I found out about the problem early!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>first light on HTTP</title>
		<published>2024-02-14T00:00:00+00:00</published>
		<updated>2024-02-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-04/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-04/</id>
		<content type="html">&lt;p&gt;Over the past few days I&#x27;ve been working on getting Kubernetes serving apps. As of this morning I have a sample web app (the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kubernetes&#x2F;examples&#x2F;tree&#x2F;master&#x2F;guestbook&quot;&gt;Kubernetes guestbook example&lt;&#x2F;a&gt;, minus Redis) running on my cluster, frontend by a load balancer, and with a certificate provisioned by Let&#x27;s Encrypt to communicate with CloudFlare, who terminate SSL from the browser with their own cert.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;I&#x27;m not going to write a blow-by-blow, but here&#x27;s how a deployment ends up being exposed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Argo CD creates all the application manifests that it sees in a Git repository. (All the cluster components are managed this way, actually, which simplifies things. No Helm server, very few manually-applied manifests.) This gets us the service, any deployments, containers, etc. But the most important thing for this is the ingress!&lt;&#x2F;li&gt;
&lt;li&gt;The app&#x27;s ingress specifies that it wants to use the cluster Nginx ingress controller, which is fronted by a load balancer. The controller picks up the ingress, adds an Nginx config for it with host-based routing, and reloads.&lt;&#x2F;li&gt;
&lt;li&gt;The app&#x27;s ingress has a &lt;code&gt;external-dns.alpha.kubernetes.io&#x2F;hostname&lt;&#x2F;code&gt; on it, which &lt;code&gt;external-dns&lt;&#x2F;code&gt; picks up once the ingress controller adds the app&#x27;s accessible IP and associates with an A record in CloudFlare.&lt;&#x2F;li&gt;
&lt;li&gt;The app&#x27;s ingress also has a TLS config and a &lt;code&gt;cert-manager.io&#x2F;cluster-issuer&lt;&#x2F;code&gt; annotation, which &lt;code&gt;cert-manager&lt;&#x2F;code&gt; picks up and uses to request a Let&#x27;s Encrypt certificate with a DNS-based challenge.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;After all those things happen, the app ends up running in the cluster and accessible to the public. When you request the specified host in the browser, CloudFront terminates TLS with their own certificate, then establish a connection with the Let&#x27;s Encrypt certificate to the load balancer, which sends the traffic to the Nginx router, which talks to the container directly.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s four hops, but some quick tests suggest that my response time is acceptable: looks like I&#x27;ve got a 50th percentile response time of 132ms, going up to 239ms at the 95th percentile under a load of about 30 requests per second. If I end up needing to optimize that, I could use a loadbalancer service instead of an ingress with the nginx ingress controller, but I think we&#x27;ll be OK for now.&lt;&#x2F;p&gt;
&lt;p&gt;Not bad for like 15 hours of work!&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, the next step in my prep will be exposing the Argo CD control plane safely. I&#x27;ve just been port forwarding it for now, which is fine, but I want to be able to send it webhooks to refresh so that it can be properly integrated with my Git server. That&#x27;ll probably mean setting up Keycloak to act as an OIDC provider and then using that to provision a Tailscale network to keep everything connected. After that, I&#x27;ll set up Argo workflows to build container images, and then I&#x27;ll be in a place to start moving more services over. I anticipate that might take the rest of the month, leaving me in a good place to build the first &quot;real&quot; thing-a-month in March.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>a Kubernetes shopping list</title>
		<published>2024-02-08T06:12:53-06:00</published>
		<updated>2024-02-08T06:12:53-06:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-03/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-03/</id>
		<content type="html">&lt;p&gt;I talked to a few people yesterday—it sounds like Kubernetes is where it&#x27;s at. I really enjoyed Nomad back in the day, but it looks like I&#x27;d have to do a lot of roll-your-own, which kind of negates the benefit of operational simplicity for me.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;That doesn&#x27;t mean that I&#x27;m going to let simplicity go out the window. I looked around and found that Vultr will manage the control plane for free as long as you pay for the worker nodes. Doesn&#x27;t look like it&#x27;s highly-available, but I think that&#x27;s OK for me for now.&lt;&#x2F;p&gt;
&lt;p&gt;So that leaves me with another shopping list to make: how do I run my stuff in Kubernetes?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CD and workload management:&lt;&#x2F;strong&gt; I&#x27;ll definitely use Argo CD for this. I used it in production at my last job and it was great.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;CI:&lt;&#x2F;strong&gt; Like I mentioned yesterday, I&#x27;ve been wanting &quot;real&quot; CI for my personal stuff for a while. It looks like I have options: maybe Argo Workflows to stay in the family, but Tekton looks fine too. I know I said I&#x27;d rather avoid Jenkins but Jenkins X is apparently a rewrite? Interesting. I could also switch Git hosting to GitLab and use their runners.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Routing and load balancing:&lt;&#x2F;strong&gt; Bunch of good options. Traefik, the Nginx ingress controller, etc. I&#x27;m most interested in Istio, though. Seems like it has a gateway thing? Worth playing around with, anyway.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Container storage:&lt;&#x2F;strong&gt; Harbor graduated from the CNCF, so probably that if I don&#x27;t get something built in with the Vultr cluster. But maybe Dragonfly, which does P2P stuff? Seems cool but I probably don&#x27;t have the problem it solves.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Misc:&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;A very experienced friend told me that cert-manager and external-dns were pretty much required, so I&#x27;m going to look into them.&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;ve also used and loved Argo Rollouts, so that&#x27;s going on the list. Not &lt;em&gt;required&lt;&#x2F;em&gt;, but they worked enough better for me than the core deployment object before and give you options for healthy rollouts so I&#x27;m going to use them for sure.&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;ll need to set up the CSI driver for Vultr too so that my persistent volumes will persist.&lt;&#x2F;li&gt;
&lt;li&gt;How do secrets work in Kubernetes now? Do I need to run some component or is the builtin stuff usually good enough?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;But, zooming out, I don&#x27;t want to spend &lt;em&gt;too&lt;&#x2F;em&gt; much time on this. It&#x27;s really easy to tweak and tweak and then never move on to the rest of what needs to happen.&lt;&#x2F;p&gt;
&lt;p&gt;So, guess I&#x27;ll start grabbing stuff and shoving it into Terraform and see what happens!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>shopping for a scheduler</title>
		<published>2024-02-07T00:00:00+00:00</published>
		<updated>2024-02-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-02/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-02/</id>
		<content type="html">&lt;p&gt;The first thing on my &quot;meta&quot; list is to get my house in order with how I deploy things. Right now I have a single VM in DigitalOcean that runs my blog, git.bytes.zone, and a couple other smaller static sites. (See &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;clown-computing&#x2F;&quot;&gt;clown computing&lt;&#x2F;a&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s starting to kind of burst at the seams, though. I haven&#x27;t done an amazing job separating databases for various things, so they all live in one big server and backup. I also can&#x27;t publish a blog post without rebuilding the entire VM in Nix. That means that yesterday, for example, I was trying to publish a blog post and Nix (via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nixbuild.net&quot;&gt;nixbuild.net&lt;&#x2F;a&gt;) was trying to recompile the Linux kernel. Those are both important things, but they need to be done separately. It&#x27;s all too messy and needs a good scrub.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;I&#x27;d like to move things into containers (or at least separate them a bit more) and I think I have two great options: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kubernetes.io&#x2F;&quot;&gt;Kubernetes&lt;&#x2F;a&gt; and HashiCorp&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.nomadproject.io&#x2F;&quot;&gt;Nomad&lt;&#x2F;a&gt;. I&#x27;ve used or consulted on deployments of both, but it&#x27;s been a while so I&#x27;m trying to brush up. Here&#x27;s what I&#x27;m trying to evaluate:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In terms of operational simplicity, I prefer Nomad. It integrates well with Consul for service discovery and Vault for secret management. Kubernetes has all-in-one distributions like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;k0sproject.io&#x2F;&quot;&gt;k0s&lt;&#x2F;a&gt; but there are still many many more control loops.&lt;&#x2F;li&gt;
&lt;li&gt;For ecosystem support, Kubernetes wins in a landslide. For example, I&#x27;d like to set up CI&#x2F;CD for my git server as part of this work (a long-standing pain point for me.) There are like a billion options for Kubernetes—dedicated &quot;cloud-native&quot; ones like Tekton or Argo, plus basically every standalone CI&#x2F;CD solution has a Kubernetes operator. And for Nomad… not many options at all. There&#x27;s Jenkins, but I&#x27;d really rather not use Jenkins if I can avoid it.&lt;&#x2F;li&gt;
&lt;li&gt;In licensing… meh, kind of a wash. HashiCorp made a splash with their license changes last year, but it doesn&#x27;t really affect my use. The enterprise features of Nomad are all things I can live without (geo-replication would be nice but worrying about that now is kind of putting the cart before the horse.)&lt;&#x2F;li&gt;
&lt;li&gt;How about resource use? Seems like Nomad wins again here but I haven&#x27;t benchmarked them. K0s seems pretty light though.&lt;&#x2F;li&gt;
&lt;li&gt;And as far as how they actually are to use: I really don&#x27;t like templating YAML, but HCL is also semi-famous for being annoying. I could just use CUE for either, probably. Nomad is better at nice deployment patterns out of the box, but Kubernetes can be extended there easily.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In short: it&#x27;s hard to decide. Both options have things to recommend them as well as clear tradeoffs (the biggest one being operational simplicity trading off for ecosystem support.) It&#x27;s also the case that either option would be fine, and once all the stuff I want is containerized moving between them is not too bad (my workloads are simple enough that ingress is going to be the pain point rather than overlay networking or service discovery.)&lt;&#x2F;p&gt;
&lt;p&gt;After publishing my last post, I actually got in contact with a couple folks who have used both of these in bigger ways than I have to see what they think. I&#x27;m curious to hear what they have to say!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>thing-a-month</title>
		<published>2024-02-06T12:00:00-06:00</published>
		<updated>2024-05-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/thing-a-month/" type="text/html"/>
		<id>https://bytes.zone/projects/thing-a-month/</id>
		<content type="html">&lt;p&gt;I&#x27;m interested in making new products, and I&#x27;ve got a bunch of ideas.
So in 2024 (or at least the early part of it) I tried to make a thing a month!&lt;&#x2F;p&gt;
&lt;p&gt;The rules were pretty simple:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;It had to be a &quot;real&quot; thing. Something you can buy, download, subscribe to, etc.&lt;&#x2F;li&gt;
&lt;li&gt;It had to be ready in a month.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If I decided I want one of the &quot;things&quot; to be improvements to an earlier thing, that would&#x27;ve been fine.
Point was: they couldn&#x27;t be huge years-long projects.&lt;&#x2F;p&gt;
&lt;p&gt;Here are the projects I did:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;February 2024 (month 0): &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month-meta&#x2F;&quot;&gt;setting the stage for the rest of the project with infra improvements&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;March+April 2024 (months 1 and 2): &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month-awareness&#x2F;&quot;&gt;awareness through random time tracking&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;April (as part of a yak shave): &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;elm-duet&#x2F;&quot;&gt;elm-duet&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I ended up having to stop doing thing-a-month because the time constraint was infeasible for the things I was interested in building. I wrote about that at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;stopping-thing-a-month&#x2F;&quot;&gt;stopping thing-a-month&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>kicking off thing-a-month</title>
		<published>2024-02-06T00:01:00-06:00</published>
		<updated>2024-02-06T00:01:00-06:00</updated>
		<link href="https://bytes.zone/micro/thing-a-month-02-01/" type="text/html"/>
		<id>https://bytes.zone/micro/thing-a-month-02-01/</id>
		<content type="html">&lt;p&gt;I&#x27;m going to try starting to make a thing a month. (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month&#x2F;&quot;&gt;Here&#x27;s the projects entry&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;p&gt;The long (looooong) term goal is financial independence, but the short-term goal is more about exercising my creative muscles, having something cool to write about, and actually trying to make the things I&#x27;ve been dreaming of.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;This first month is a meta-month. I know for the rest of the months I do this project I&#x27;ll want to be able to ship things as quickly as possible and not spend my precious time on infrastructure or setup, so I&#x27;m going to see about frontloading most of that this month.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>meta: micro posts are in the feed now</title>
		<published>2024-02-06T00:00:00-06:00</published>
		<updated>2024-02-06T00:00:00-06:00</updated>
		<link href="https://bytes.zone/micro/meta-micro-posts-in-feed/" type="text/html"/>
		<id>https://bytes.zone/micro/meta-micro-posts-in-feed/</id>
		<content type="html">&lt;p&gt;Hi out there to any folks subscribing via RSS! You probably got a bunch of posts in your feed reader just now that had to do with an exploration of starting a business I did in August of last year. And with the next post in your feed, you&#x27;re going to start getting more like that.&lt;&#x2F;p&gt;
&lt;p&gt;If that&#x27;s cool with you, then great: no action required on your part to get everything.
However, if you&#x27;d like to be more picky about which of my posts you get, re-point your feed reader to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;index.xml&quot;&gt;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;index.xml&lt;&#x2F;a&gt; for only deeper posts&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;index.xml&quot;&gt;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;index.xml&lt;&#x2F;a&gt; for only micro posts&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;index.xml&quot;&gt;https:&#x2F;&#x2F;bytes.zone&#x2F;index.xml&lt;&#x2F;a&gt; for everything&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;BTW, about the &quot;micro&quot; name: I started this thinking of these as truly tiny, but they ended up being simply looser and less well-researched than the bigger posts I do. They may end up being more like 140-character things sometimes, but the real point is to &quot;work with the garage door up&quot; and give you a look at what I&#x27;m doing on a more regular basis.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>thing-a-month (meta)</title>
		<published>2024-02-06T00:00:00-06:00</published>
		<updated>2024-02-06T00:00:00-06:00</updated>
		<link href="https://bytes.zone/projects/thing-a-month-meta/" type="text/html"/>
		<id>https://bytes.zone/projects/thing-a-month-meta/</id>
		<content type="html">&lt;p&gt;The first project in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;thing-a-month&#x2F;&quot;&gt;thing-a-month&lt;&#x2F;a&gt; will be laying the foundation for the rest of the work. (Here&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;thing-a-month-02-01&#x2F;&quot;&gt;the intro post where I talk about this&lt;&#x2F;a&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been running my own infrastructure for a while now but it&#x27;s gotten really messy recently. Instead of throwing more stuff into that, I&#x27;m going to try and get things set up in a less chaotic way. This might seem like overkill, but I see benefits I could get immediately!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>modeling CRDTs in Alloy - counters</title>
		<published>2023-11-27T00:00:00+00:00</published>
		<updated>2023-11-27T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-crdts-in-alloy-counters/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-crdts-in-alloy-counters/</id>
		<content type="html">&lt;p&gt;Hey, welcome back! &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-crdts-in-alloy-introduction-and-the-importance-of-idempotence&#x2F;&quot;&gt;Last time, we introduced conflict-free replicated datatypes (CRDTs) and modeled them in Alloy by using boolean &lt;code&gt;OR&lt;&#x2F;code&gt; as our merge function&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As a quick recap, CRDTs give you eventual consistency, no matter how out of sync the data originally was. This makes them great for local-first or networked multiplayer applications. If you can write a &lt;code&gt;merge&lt;&#x2F;code&gt; function for a data structure that is commutative, associative, and idempotent, you might be able to build a CRDT on top of it.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Further, we&#x27;re modeling all these semantics in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.org&quot;&gt;Alloy&lt;&#x2F;a&gt;, a lightweight formal methods tool good for modeling and analyzing data structures. We&#x27;ll see some cool stuff it can do in a second.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;counting-birds&quot;&gt;Counting Birds&lt;&#x2F;h2&gt;
&lt;p&gt;Last time our document was a single boolean. That&#x27;s useful, but we can&#x27;t build an application on top of it, so let&#x27;s keep moving. To get much further, we&#x27;re going to have to figure out how to sync numbers back and forth. Let&#x27;s use the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.birdcount.org&#x2F;&quot;&gt;the Great Backyard Bird Count&lt;&#x2F;a&gt; as an example. Say we&#x27;re counting &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.allaboutbirds.org&#x2F;guide&#x2F;Red-tailed_Hawk&#x2F;id&quot;&gt;red-tailed hawks&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Everyone starts at zero.&lt;&#x2F;li&gt;
&lt;li&gt;Person A sees a hawk! Ka-kaw! They increment the counter by one.&lt;&#x2F;li&gt;
&lt;li&gt;Person B sees another hawk! Screeee! They increment the counter by one, too.&lt;&#x2F;li&gt;
&lt;li&gt;Person C sees &lt;em&gt;two&lt;&#x2F;em&gt; hawks. Wow! Of course, they increment the counter by two.&lt;&#x2F;li&gt;
&lt;li&gt;Day&#x27;s over; good job everyone! Sync up your apps and lets see how many hawks we saw total.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;At first glance, we might want to use &lt;code&gt;+&lt;&#x2F;code&gt; as the merge function. But remember, that&#x27;s not idempotent. So this might happen among three people:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The group has seen four hawks total. Person A and person B have seen 1 each, person C has seen 2. Nobody has talked to anyone else.&lt;&#x2F;li&gt;
&lt;li&gt;Person A and person B sync and set both their totals to &lt;code&gt;1 + 1 = 2&lt;&#x2F;code&gt; (one from each, the initial count)&lt;&#x2F;li&gt;
&lt;li&gt;Person A and person C sync and set both their totals to &lt;code&gt;2 + 2 = 4&lt;&#x2F;code&gt; (two from A because of the previous sync)&lt;&#x2F;li&gt;
&lt;li&gt;Person B and person C sync and set both their totals to &lt;code&gt;2 + 4 = 6&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Further syncs happen, and the result increases every time.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;In other words, we can return with a huge number of hawks when really we only saw four. That&#x27;s not what we wanted! This fails because &lt;code&gt;+&lt;&#x2F;code&gt; is not idempotent: adding any meaningful number will always change the result.&lt;&#x2F;p&gt;
&lt;p&gt;What if we used &lt;code&gt;max&lt;&#x2F;code&gt; instead? It&#x27;s commutative, associative, and idempotent, but the semantics don&#x27;t work out here: if we used &lt;code&gt;max&lt;&#x2F;code&gt; to merge, we&#x27;d only get to see the max of hawks that any &lt;em&gt;one person&lt;&#x2F;em&gt; had seen.&lt;&#x2F;p&gt;
&lt;p&gt;However, &lt;code&gt;max&lt;&#x2F;code&gt; would work if we synced one counter for each person and then added them together locally to get the result! Let&#x27;s look at an example:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Just like before, the group has seen four hawks total. Person A and person B have seen 1 each, person C has seen 2. Nobody has talked to anyone else.&lt;&#x2F;li&gt;
&lt;li&gt;Person A and person B sync their totals, going from person A&#x27;s &lt;code&gt;{a: 1}&lt;&#x2F;code&gt; and person B&#x27;s &lt;code&gt;{b: 1}&lt;&#x2F;code&gt; to &lt;code&gt;{a: 1, b: 1}&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Person A and person C sync their totals, going from person A&#x27;s &lt;code&gt;{a: 1, b: 1}&lt;&#x2F;code&gt; and person C&#x27;s &lt;code&gt;{c: 2}&lt;&#x2F;code&gt; to &lt;code&gt;{a: 1, b: 2, c: 2}&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Person B and person C sync their totals, finishing the sync. Now everyone has &lt;code&gt;{a: 1, b: 1, c: 2}&lt;&#x2F;code&gt; and anyone can compute that we&#x27;ve seen four hawks total.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;When you have a conflicting value (say B saw another hawk later) you take the &lt;code&gt;max&lt;&#x2F;code&gt; of &lt;em&gt;that person&#x27;s&lt;&#x2F;em&gt; count when syncing.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This also has the nice property of moving information through the system without requiring everyone to talk to everyone else. Consider what would happen if we did A+B, A+C, A+B instead of ending with B+C. We&#x27;d end up with the same value, since A+C would have given A the count to pass along to B, except that B and C never have to talk directly. It&#x27;s eventually consistent!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;moving-to-alloy&quot;&gt;Moving to Alloy&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s model this scheme in Alloy. We&#x27;ll model an ID to represent each individual birdwatcher:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; ID&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;ll also model a &lt;code&gt;Document&lt;&#x2F;code&gt; to allow each person to keep their count and the count of each of their peers:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Document&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; ID&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  var counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ID &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt; lone&lt;&#x2F;span&gt;&lt;span&gt; Natural&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;A -&amp;gt; lone B&lt;&#x2F;code&gt; means &quot;each A maps to at most one B.&quot; In other words, it makes &lt;code&gt;ID&lt;&#x2F;code&gt; unique in &lt;code&gt;counts&lt;&#x2F;code&gt; for each &lt;code&gt;Document&lt;&#x2F;code&gt; (the way you&#x27;d expect a dictionary to work in an implementation.)&lt;&#x2F;p&gt;
&lt;p&gt;We have an error already, though. When I run this, Alloy gives me and edge case where we use the same ID for multiple documents:&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;duplicate-ids-in-counter-document.png&quot; alt=&quot;An Alloy instance showing two documents using the same ID.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If this happened we&#x27;d lose data since we couldn&#x27;t distinguish between values we needed to add and those we needed to &lt;code&gt;max&lt;&#x2F;code&gt;. Better disallow that in the model:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;no&lt;&#x2F;span&gt;&lt;span&gt; two documents can have the same ID&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; &amp;quot;disj&amp;quot; means d1 and d2 can&amp;#39;t be the same document&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no disj&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As always, adding a &lt;code&gt;fact&lt;&#x2F;code&gt; weakens our spec by making assumptions. So let&#x27;s think: can we actually get rid of ID collisions when we implement this spec? Well, maybe… in real life, we&#x27;d use some ID scheme that we were reasonably sure would never collide. In production CRDTs, it seems like this means using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Universally_unique_identifier&quot;&gt;UUIDs&lt;&#x2F;a&gt; a lot of the time. Good enough for me; let&#x27;s move on.&lt;&#x2F;p&gt;
&lt;p&gt;With this change, we can get some more interesting instances of the data structure. For example, here&#x27;s what it could look like when these &lt;code&gt;counts&lt;&#x2F;code&gt; maps are filled out:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;two-counters-with-conflicting-counts.png&quot; alt=&quot;An Alloy instance showing two documents with various counts in need of syncing.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;(You don&#x27;t need to know how to read this example, but if you want to: the keys in the map are represented by the labels on the arrows. For example, &quot;Document 0 has a count for ID 1 of 0&quot;.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;merging&quot;&gt;Merging&lt;&#x2F;h2&gt;
&lt;p&gt;We don&#x27;t have a way to sync yet, so let&#x27;s start fixing that. Let&#x27;s define &lt;code&gt;merge&lt;&#x2F;code&gt;, as well as adding checks that it satisfies our three laws:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ID &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt; lone&lt;&#x2F;span&gt;&lt;span&gt; Natural]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (ID &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt; lone&lt;&#x2F;span&gt;&lt;span&gt; Natural) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; intent: set each value in the document to the maximum value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; of that key in all the documents we&amp;#39;re currently merging.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; allCounts &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;d2 &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    { id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ID&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Natural &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;count &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; allCounts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        and&lt;&#x2F;span&gt;&lt;span&gt; count &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; nat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&#x2F;max&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;allCounts&lt;&#x2F;span&gt;&lt;span&gt;[id]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsCommutative&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsAssociative&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    merge&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsIdempotent&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alloy says it can&#x27;t find a counterexample for these three checks, so it looks like we might be in business.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;actions&quot;&gt;Actions&lt;&#x2F;h2&gt;
&lt;p&gt;Next let&#x27;s set up some things our documents can do. First, we should be able to increment a number in a document by removing the old count for this document and ID and adding the new one:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; increment&lt;&#x2F;span&gt;&lt;span&gt;[d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; current &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;counts&lt;&#x2F;span&gt;&lt;span&gt;[d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    counts&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      counts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;current&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      +&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;bounded_inc&lt;&#x2F;span&gt;&lt;span&gt;[current]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;About all the arrows: &lt;code&gt;a-&amp;gt;b-&amp;gt;c&lt;&#x2F;code&gt; creates a three-valued relation (you can think of it like a 3-tuple if it helps: &lt;code&gt;(a, b, c)&lt;&#x2F;code&gt;.) Since &lt;code&gt;counts&lt;&#x2F;code&gt; is actually a mapping of document-to-id-to-natural, we need this level of precision. However, this is the only place we&#x27;ll this since writing &lt;code&gt;d.counts&lt;&#x2F;code&gt; will pop the &lt;code&gt;Document&lt;&#x2F;code&gt; off the left-hand side of the relation, leaving us with &lt;code&gt;ID -&amp;gt; Natural&lt;&#x2F;code&gt; (as in our &lt;code&gt;sig&lt;&#x2F;code&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;I had to write a &lt;code&gt;bounded_inc&lt;&#x2F;code&gt; helper function to write &lt;code&gt;increment&lt;&#x2F;code&gt;. Here it is:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; bounded_inc&lt;&#x2F;span&gt;&lt;span&gt;[n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Natural]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Natural {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  n &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; nat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&#x2F;last&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; implies&lt;&#x2F;span&gt;&lt;span&gt; nat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&#x2F;last&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span&gt; nat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&#x2F;inc&lt;&#x2F;span&gt;&lt;span&gt;[n]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This was necessary because Alloy&#x27;s numbers are actually just regular &lt;code&gt;sig&lt;&#x2F;code&gt;s with an ordering.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Since we only deal with a bounded number of instances for each sig, addition and counting has a gotcha. Say you only knew the numbers 1 and 2: counting works fine up to a certain point, but if someone asked you &quot;ok, and what&#x27;s after two?&quot; you wouldn&#x27;t know. This is exactly what&#x27;s going on with Alloy, which will return a blank set if you run out of numbers. Specs can define semantics specific to their applications, though, and in ours I&#x27;ve decided that incrementing past the max is simply not allowed—a number will just stop growing a certain point.&lt;&#x2F;p&gt;
&lt;p&gt;This should not really affect the soundness of our spec, but choosing limits like this requires careful thought. Alloy is not designed for numerical computing, and forcing it means dealing with weird edge cases like this. Fortunately, that&#x27;s the only place in the spec we should have to deal with this.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;now-we-can-sync&quot;&gt;Now We Can Sync!&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s move on to &lt;code&gt;sync&lt;&#x2F;code&gt;, which should merge two documents:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; merged &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;counts] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    counts&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; counts &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt; (d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;merged &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;merged)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next the little bit of boilerplate that we need to treat these as actions:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; init&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; intent: set every document to 0 for its own ID&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  counts &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; { d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ID&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Zero &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; id }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; traces&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;some&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; increment&lt;&#x2F;span&gt;&lt;span&gt;[d])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;some&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, let&#x27;s check idempotence &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-crdts-in-alloy-introduction-and-the-importance-of-idempotence&#x2F;&quot;&gt;like last time&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; SyncIsIdempotent&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2]; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2]) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; counts&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; counts&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alloy can&#x27;t find a counterexample for this, so I think we&#x27;re done! These documents should work as CRDTs! In fact, our counters here model something called a &quot;GCounter&quot; (a &quot;grow-only&quot; counter.) There are plenty of implementations out there if you want search for that term and find some real code to read!&lt;&#x2F;p&gt;
&lt;p&gt;But… GCounters can only count up. That&#x27;s a problem, right? You might think &quot;ah, we can get around that—each node could set its own count to whatever it wanted!&quot; But we&#x27;d hit problems there on the first sync: since our &lt;code&gt;merge&lt;&#x2F;code&gt; function uses &lt;code&gt;max&lt;&#x2F;code&gt;, any time a node&#x27;s counter decremented it&#x27;d go back up to the highest-seen value on the next sync.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a nice way out of this predicament, though: use two counters per document, &lt;code&gt;positive&lt;&#x2F;code&gt; and &lt;code&gt;negative&lt;&#x2F;code&gt;. Both counters always go up (so &lt;code&gt;max&lt;&#x2F;code&gt; doesn&#x27;t give us trouble) and when you need to get the value out of the document, you sum up all the values of both counters, then get the final value from &lt;code&gt;positive - negative&lt;&#x2F;code&gt;. Ta-da! The final can now go up and down, and you have something called a &quot;PNCounter&quot; (a &quot;positive&#x2F;negative&quot; counter.)&lt;&#x2F;p&gt;
&lt;p&gt;You might also look at this and think &quot;yikes, that looks like a lot of memory for a single number… that would just be a single integer if this wasn&#x27;t distributed.&quot; Yep! There&#x27;s always going to be some overhead for syncing. You can minimize it, though, and people who make production-class CRDTs (like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.inkandswitch.com&#x2F;&quot;&gt;Ink and Switch&lt;&#x2F;a&gt;) clearly think about data compression a lot! In fact, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jakelazaroff.com&#x2F;words&#x2F;making-crdts-98-percent-more-efficient&#x2F;&quot;&gt;Jake Lazaroff has written an excellent blog post about doing this in an image-based CRDT&lt;&#x2F;a&gt;. Go read that next!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Thanks to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.discourse.group&#x2F;t&#x2F;merging-two-sets-of-relations-by-some-rule&#x2F;407&quot;&gt;Alcino Cunha on the Alloy forums for helping me understand how set comprehensions could be used for &lt;code&gt;merge&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, as well as pointing out that it should be possible to specify our checks with only events. Thanks also to Jake Lazaroff for reviewing an early draft and suggesting better ways to structure this.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;This only works if you can trust the people doing the counts, though. If person B says &quot;wow, I heard person A saw FIFTY HAWKS&quot; then that information will spread without any recourse for A to say &quot;er, no, I only saw one.&quot; These kind of security and sharing situations seems to be open questions in the field. I guess you could sign values, but that&#x27;d add a lot of overhead in any decently-sized document. Tricky!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Images throughout this post are generated by Alloy—that&#x27;s one of the things I like best about it. Visualizing edge cases like this makes it really easy to talk about potential drawbacks to modeling with other engineers or people making product decisions.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;That is, the numbers you work with &lt;em&gt;in sigs&lt;&#x2F;em&gt;. You can get the cardinality of a set (e.g. &lt;code&gt;#Document&lt;&#x2F;code&gt;) without these restrictions. However, you can&#x27;t store the cardinality in a &lt;code&gt;sig&lt;&#x2F;code&gt;, so we still have to work around this restriction.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;4&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;Briefly: you can make a GSet (grow-only set) whose &lt;code&gt;merge&lt;&#x2F;code&gt; is the union of two sets. That gives you the set equivalent of our GCounter—it can only add new items. To remove items, you do the same trick as PNCounter and keep two sets, then get the final value by computing &lt;code&gt;union(add) - union(remove)&lt;&#x2F;code&gt;. This means that you can never re-add an item that you&#x27;ve removed, though. We&#x27;ll touch on how to get around that next time.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>sticky table headers with bottom borders</title>
		<published>2023-11-13T00:00:00+00:00</published>
		<updated>2023-11-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/sticky-table-headers-with-bottom-borders/" type="text/html"/>
		<id>https://bytes.zone/posts/sticky-table-headers-with-bottom-borders/</id>
		<content type="html">&lt;p&gt;Say you have a table like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;table&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;thead&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;tr&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;th&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Column 1&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;th&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;th&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Column 2&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;th&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;th&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Column 3&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;th&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;tr&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;thead&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;tbody&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &amp;lt;!-- ... --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;tbody&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;table&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You might try to separate the header from the body like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;table&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.fancy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; thead&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; tr&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  border-bottom&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; solid #ccc&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That works, but what if you want the &lt;code&gt;&amp;lt;thead&amp;gt;&lt;&#x2F;code&gt; element to have &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;CSS&#x2F;position#values&quot;&gt;&lt;code&gt;position: sticky&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; too? If you used a border, the border will keep scrolling while the header sticks. Oh no!&lt;&#x2F;p&gt;
&lt;p&gt;I got around this by using &lt;code&gt;box-shadow&lt;&#x2F;code&gt; instead. It works, despite feeling a little hacky:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;table&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.fancy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; thead&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; tr&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  box-shadow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; inset 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; #ccc&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Except that Safari won&#x27;t display the shadow below the header. More hacks required! Apply the shadow to all the cells inside it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;table&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.fancy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; thead td&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  box-shadow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; inset 0 -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; #ccc&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This feels even hackier than the original fix, but it works fine in all the browsers that I tested!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>engineering design systems at Vendr</title>
		<published>2023-10-23T00:00:00+00:00</published>
		<updated>2024-08-30T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/staff-engineer-at-vendr/" type="text/html"/>
		<id>https://bytes.zone/projects/staff-engineer-at-vendr/</id>
		<content type="html">&lt;p&gt;In late October 2023, I started working at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;vendr.com&quot;&gt;Vendr&lt;&#x2F;a&gt; as a staff design systems engineer.
In that role, I work with both the design and engineering teams at Vendr to make making a consistent user experience easier.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve notched some notable performance wins at Vendr!
For example, I reduced our first contentful paint metric by about 2 seconds in the first quarter of 2024, which brought largest contentful paint down as well.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>modeling CRDTs in Alloy - introduction and the importance of idempotence</title>
		<published>2023-10-09T00:00:00+00:00</published>
		<updated>2023-10-09T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-crdts-in-alloy-introduction-and-the-importance-of-idempotence/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-crdts-in-alloy-introduction-and-the-importance-of-idempotence/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been interested in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.inkandswitch.com&#x2F;local-first&#x2F;&quot;&gt;local-first software&lt;&#x2F;a&gt; for a long time, and recently attended an event about it with a bunch of luminaries from various research groups. I learned a lot, and it rekindled my interest in syncable data structures.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve tried to sync data over the years with varying degrees of success. For example, I&#x27;ve known about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Operational_transformation&quot;&gt;operational transformations (OT)&lt;&#x2F;a&gt; for a while (via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;quilljs.com&#x2F;&quot;&gt;Quill&lt;&#x2F;a&gt;, but I have never been able to get the syncing operations working to my satisfaction. Message ordering is crucially important in OT, and it&#x27;s hard to get right. I get the impression that the engineers who worked on Google Wave (which also used OT) also struggled with this. One of them (Joseph Gentle) was quoted as saying &quot;Unfortunately, implementing OT sucks. There&#x27;s a million algorithms with different tradeoffs, mostly trapped in academic papers. […] Wave took 2 years to write and if we rewrote it today, it would take almost as long to write a second time.&quot; (Worth noting that &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=12311984&quot;&gt;he later said it might not take as long today&lt;&#x2F;a&gt;, but still… pain!)&lt;&#x2F;p&gt;
&lt;p&gt;In contrast to OT, conflict-free replicated data types (CRDTs) seem really promising! &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Conflict-free_replicated_data_type&quot;&gt;Wikipedia has a good summary&lt;&#x2F;a&gt;, which I&#x27;ll quote:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;In distributed computing, a conflict-free replicated data type (CRDT) is a data structure that is replicated across multiple computers in a network, with the following features:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The application can update any replica independently, concurrently and without coordinating with other replicas.&lt;&#x2F;li&gt;
&lt;li&gt;An algorithm (itself part of the data type) automatically resolves any inconsistencies that might occur.&lt;&#x2F;li&gt;
&lt;li&gt;Although replicas may have different state at any particular point in time, they are guaranteed to eventually converge.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;These properties appeal to me! If CRDTs can automatically and independently converge, no matter how far apart they drift, then you could store them wherever and however you like. Offline support should be simple, and adding live collaboration features should not mean spending years figuring out edge cases. Sign me up!&lt;&#x2F;p&gt;
&lt;p&gt;What are CRDTs made from, though? It looks like typically it&#x27;s some internal stuff, a function to resolve the internal stuff to a &lt;code&gt;value&lt;&#x2F;code&gt;, and a function to &lt;code&gt;merge&lt;&#x2F;code&gt; two of the data structure together. The merge function is the number-one-most-important thing about this whole arrangement, and has to satisfy&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; these three laws:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;commutative (so &lt;code&gt;merge(a, b)&lt;&#x2F;code&gt; must be the same as &lt;code&gt;merge(b, a)&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;associative (so &lt;code&gt;merge(merge(a, b), c)&lt;&#x2F;code&gt; must be the same as &lt;code&gt;merge(a, merge(b, c))&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;idempotent (so &lt;code&gt;merge(a, b)&lt;&#x2F;code&gt; must be the same as &lt;code&gt;merge(merge(a, b), b)&lt;&#x2F;code&gt;.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If all these are true, we can always merge two states, regardless of how many changes have been made since we last merged, and get the same result on both sides of the sync. Cool!&lt;&#x2F;p&gt;
&lt;p&gt;These seem like the kinds of things that &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;learning-alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; would be good at modeling, so let&#x27;s try it! Over the next couple of posts, I&#x27;m going to build a handful of data structures that follow these three rules and compose them into data structures we could build an application on top of.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s get a taste for this with &lt;code&gt;OR(bool, bool)&lt;&#x2F;code&gt;. First off, does it satisfy our three properties?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;commutative: &lt;code&gt;OR(True, False)&lt;&#x2F;code&gt; is the same as &lt;code&gt;OR(False, True)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;associative: &lt;code&gt;OR(OR(True, False), True)&lt;&#x2F;code&gt; is the same as &lt;code&gt;OR(True, OR(False, True))&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;idempotent: calling &lt;code&gt;OR(x, True)&lt;&#x2F;code&gt; gives the same result as &lt;code&gt;OR(OR(x, True), True)&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So it looks like &lt;code&gt;OR&lt;&#x2F;code&gt; might be fine, but let&#x27;s be sure. We can check this in Alloy by first defining&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; a boolean:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;enum Bool { True&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; False }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then a merge function, which we check is correct:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; OR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  a &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; True &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; a &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsCorrect&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; read as: given `a` and `b`, which are `Bool`, the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; following condition must hold.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (a &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; True &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;or&lt;&#x2F;span&gt;&lt;span&gt; b &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; True) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And finally, our three properties:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsCommutative&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; a]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsAssociative&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; c] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; c]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MergeIsIdempotent&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Great! Alloy can&#x27;t find any errors with &lt;code&gt;OR&lt;&#x2F;code&gt;.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#4&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; We expected that, so let&#x27;s break it in interesting ways. What if the merge function was &lt;code&gt;XOR(bool, bool)&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#5&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; instead?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  a &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; b &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; False &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now Alloy finds that we&#x27;ve broken idempotency: &lt;code&gt;merge[False, True]&lt;&#x2F;code&gt; is &lt;code&gt;True&lt;&#x2F;code&gt;, but &lt;code&gt;merge[merge[False, True], True]&lt;&#x2F;code&gt; is &lt;code&gt;False&lt;&#x2F;code&gt;. This would cause big problems if we used &lt;code&gt;XOR&lt;&#x2F;code&gt; as a merge function: the act of syncing could change our values! We can imagine this sequence of events:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Node A sets the value to &lt;code&gt;True&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Node B sets the value to &lt;code&gt;False&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The nodes sync, both resolving to &lt;code&gt;True&lt;&#x2F;code&gt; (since &lt;code&gt;XOR(True, False)&lt;&#x2F;code&gt; is &lt;code&gt;True&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;The nodes sync again, both resolving to &lt;code&gt;False&lt;&#x2F;code&gt; (since &lt;code&gt;XOR(True, True)&lt;&#x2F;code&gt; is &lt;code&gt;False&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Further syncs would stay at &lt;code&gt;False&lt;&#x2F;code&gt; until someone changed the value to &lt;code&gt;True&lt;&#x2F;code&gt;, at which point we&#x27;d repeat from step 3.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;But in a fun twist of fate, we don&#x27;t &lt;em&gt;have&lt;&#x2F;em&gt; to imagine this; we can ask Alloy to generate these traces for us. First we&#x27;ll make a document that contains a changeable value:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Document&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  var value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Bool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then we&#x27;ll define a &lt;code&gt;not&lt;&#x2F;code&gt; for our boolean and a couple of operations (flipping an arbitrary value and syncing two documents):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; bool_not&lt;&#x2F;span&gt;&lt;span&gt;[b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Bool {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; True &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; False &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; intent: when `flip` is true, the provided document will go&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; from True to False or False to True in the next time step.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; This serves as our proxy for someone clicking a button in&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; a UI or whatever.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; flip&lt;&#x2F;span&gt;&lt;span&gt;[d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; implementation: `value` is actually a set of tuples from&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; documents to booleans, and `value&amp;#39;` is the same but in the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; next time step. We set the entire value explicitly&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; (instead of just saying like `d&amp;#39;.value = False`) because&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; otherwise Alloy could say &amp;quot;and what if something changes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; that you weren&amp;#39;t expecting?&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; Syntax: `-&amp;gt;` creates a tuple, and `++` replaces all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; existing tuples referencing `d` in the set.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  value&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;bool_not&lt;&#x2F;span&gt;&lt;span&gt;[d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; intent: merge two documents together in a way that all the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; `merge` rules hold. Maybe imagine we have `d1` locally and&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; are getting `d2` over the network (but remember that order&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; should not matter!)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; merged &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; merge&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    value&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt; (d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;merged &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;merged)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now wire them up so these events can happen over time:&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#6&quot;&gt;6&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; init&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; `Document` is a set of documents, and `-&amp;gt;` will apply pairwise&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; for the sets in its arguments, so this means this says &amp;quot;all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; documents start at `False`&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; False&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; traces&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; this says &amp;quot;start with `init`, then pick one of these things to&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; happen at each time step.&amp;quot; Putting it in a `fact` means that&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; any assertions we make from now on assume that we want these&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; instructions.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one&lt;&#x2F;span&gt;&lt;span&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; flip&lt;&#x2F;span&gt;&lt;span&gt;[d])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;some&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now check that our sync is idempotent:&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#7&quot;&gt;7&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; SyncIsIdempotent&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; at all times, if sync happens twice in a row (the `;` syntax) then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; the next value (`value&amp;#39;`) and the one after that (`value&amp;#39;&amp;#39;`) should&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; be the same.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2]; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2]) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; value&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; value&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we make &lt;code&gt;OR&lt;&#x2F;code&gt; our &lt;code&gt;merge&lt;&#x2F;code&gt; function, this works fine and Alloy can&#x27;t find any counterexamples. If we use &lt;code&gt;XOR&lt;&#x2F;code&gt; for &lt;code&gt;merge&lt;&#x2F;code&gt;, though, Alloy finds the trace I mentioned could show up earlier. Here&#x27;s what it shows us.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Step&lt;&#x2F;th&gt;&lt;th&gt;Description&lt;&#x2F;th&gt;&lt;th&gt;Image&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1.&lt;&#x2F;td&gt;&lt;td&gt;We start off with all documents at &lt;code&gt;False&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;td&gt;&lt;img src=&quot;&#x2F;images&#x2F;xor-trace-step-1-and-4.png&quot; alt=&quot;an Alloy instance showing two documents both set to False&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2.&lt;&#x2F;td&gt;&lt;td&gt;One of the documents flips to &lt;code&gt;True&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;td&gt;&lt;img src=&quot;&#x2F;images&#x2F;xor-trace-step-2.png&quot; alt=&quot;the previous instance, but with one document now set to True&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3.&lt;&#x2F;td&gt;&lt;td&gt;The documents sync, converging to &lt;code&gt;True&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;td&gt;&lt;img src=&quot;&#x2F;images&#x2F;xor-trace-step-3.png&quot; alt=&quot;the previous instance, but with both documents now set to True&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4.&lt;&#x2F;td&gt;&lt;td&gt;The documents sync again, converging to &lt;code&gt;False&lt;&#x2F;code&gt; because &lt;code&gt;XOR(True, True)&lt;&#x2F;code&gt; (the previously synced state) is &lt;code&gt;False&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;td&gt;&lt;img src=&quot;&#x2F;images&#x2F;xor-trace-step-1-and-4.png&quot; alt=&quot;the previous instance, but with both documents now set to False&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;5.&lt;&#x2F;td&gt;&lt;td&gt;Stay here forever or return to 2.&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;In case you&#x27;re encountering them for the first time, these diagrams are generated by Alloy—they&#x27;re pretty fantastic for showing other people exactly what can happen in a system. (That&#x27;s also why &lt;code&gt;Document 0&lt;&#x2F;code&gt; is &lt;code&gt;d1&lt;&#x2F;code&gt; and so on. The documents are defined separately, and the &lt;code&gt;d1&lt;&#x2F;code&gt; and &lt;code&gt;d2&lt;&#x2F;code&gt; labels come from our &lt;code&gt;check&lt;&#x2F;code&gt; so we can see what&#x27;s doing what.)&lt;&#x2F;p&gt;
&lt;p&gt;Alloy can do more than this, too. If we modify our condition to have 3 distinct documents like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; SyncIsIdempotent&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all disj&lt;&#x2F;span&gt;&lt;span&gt; d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Document &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span&gt;[d1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d2]; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span&gt;[d2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; d3]) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; value&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; value&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then Alloy shows the situation getting even sillier. You only need one of the nodes to be &lt;code&gt;False&lt;&#x2F;code&gt;, at which point it&#x27;s possible that the documents as a whole could never converge again and just flap back and forth randomly forever. It all depends on which documents sync with which other documents. Turns out idempotency is pretty important!&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s it for today. Next time we&#x27;ll go from booleans-of-dubious-usefulness to counters!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Big thanks to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jakelazaroff.com&#x2F;&quot;&gt;Jake Lazaroff&lt;&#x2F;a&gt; for his help reviewing this post. If you&#x27;re interested in this stuff, he also recently published &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jakelazaroff.com&#x2F;words&#x2F;an-interactive-intro-to-crdts&#x2F;&quot;&gt;an interactive intro to CRDTs&lt;&#x2F;a&gt; that you may enjoy!&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;This is generally true but it&#x27;s not always true when syncing. There&#x27;s a further distinction here between operation- and state-based CRDTs. I&#x27;m not going to get into it right now, but if I understand correctly, this distinction is about reducing the data you have to transfer by relaxing one of the three requirements while adding strict causal message ordering. For now, we&#x27;re going to model state-based CRDTs, where this won&#x27;t come up.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;I think argument position doesn&#x27;t matter for idempotency, since commutativity and associativity should mean we can rearrange the arguments however we like, but I don&#x27;t have a proof of this.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;Alloy has a boolean type I could just import, but it&#x27;s useful to define exactly the operations you want to understand the system better, so I&#x27;ve done that instead.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;4&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;Note that I&#x27;m not saying &quot;Alloy &lt;em&gt;proved&lt;&#x2F;em&gt; that this is correct.&quot; That&#x27;s intentional! Alloy doesn&#x27;t do proofs; it only exhaustively checks models up to a certain bound (four instances of each &lt;code&gt;sig&lt;&#x2F;code&gt; by default.) I have only had a handful of times where I&#x27;ve had to increase the bound, though. In practice, I&#x27;ve been more than happy to trade exhaustiveness for speed.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;5&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;Exclusive OR. In other words, &quot;both of these can&#x27;t be &lt;code&gt;True&lt;&#x2F;code&gt;.&quot; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Exclusive_or&quot;&gt;Wikipedia has more details, as usual&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;6&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;6&lt;&#x2F;sup&gt;
&lt;p&gt;I&#x27;m skipping over a lot of detail here. If this is your first time seeing this, there are more details and explanation on the setup here at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-git-internals-in-alloy-part-1-blobs-and-trees&#x2F;&quot;&gt;modeling Git internals in Alloy&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;7&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;7&lt;&#x2F;sup&gt;
&lt;p&gt;I&#x27;m &lt;em&gt;pretty sure&lt;&#x2F;em&gt; this is the right way to check this, but I had some trouble working it out and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.discourse.group&#x2F;t&#x2F;how-to-assert-an-operation-is-idempotent&#x2F;405&quot;&gt;had to ask on the Alloy forum&lt;&#x2F;a&gt;. If you try to do something similar and run into difficult, you might try asking there too!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Modeling in-flight requests in Alloy</title>
		<published>2023-10-02T00:00:00+00:00</published>
		<updated>2023-10-02T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-in-flight-requests-in-alloy/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-in-flight-requests-in-alloy/</id>
		<content type="html">&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;learning-alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;, sometimes I want to model clients and servers (or any relationship where two entities are talking.) With network stuff, it&#x27;s always possible for a message to not be received, or to be received twice, or out of order, et cetera. I&#x27;d like to model that!&lt;&#x2F;p&gt;
&lt;p&gt;Because I just want to learn the basics here, I&#x27;ll try to keep this as simple as possible. So instead of requests and responses or any other protocol, we&#x27;ll stick to just messages:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Message&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Delivered&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Message&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In English, &quot;We have a set of &lt;code&gt;Message&lt;&#x2F;code&gt;s, some of which have been delivered and some of which haven&#x27;t. Both sets can change over time.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;In addition to the things we define in a model like this, we have to think about the world they live in. Here I&#x27;m imagining that these messages form a stream going from some node A to another node B—no return messages, though, only one way!&lt;&#x2F;p&gt;
&lt;p&gt;Next we&#x27;ll take care of a little boilerplate: how the system starts (with no messages) and a do-nothing message so our traces can run forever.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; init&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; do_nothing&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Message&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Delivered&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Delivered&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(&lt;code&gt;Message&#x27;&lt;&#x2F;code&gt; means &quot;&lt;code&gt;Message&lt;&#x2F;code&gt; in the next step.&quot; If this is unfamiliar to you, here&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-git-internals-in-alloy-part-3-operations-on-blobs-and-trees&#x2F;&quot;&gt;a previous post where I went into more detail about how time works in Alloy&lt;&#x2F;a&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;Next we want to send a message:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; send_message&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; action: we want exactly `one` new message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; without making any other changes to `Messages`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Message&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Message {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Message&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Message &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; m&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame (holding everything else still)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Delivered&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Delivered&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can do something similar to model the request being received:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; receive_message&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; action: mark exactly one message as delivered.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Message &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Delivered {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Delivered&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Delivered &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; m&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Message&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that this can&#x27;t be true (that is, can&#x27;t happen) if we don&#x27;t have at least one message in &lt;code&gt;Message - Delivered&lt;&#x2F;code&gt;, which can read as &quot;The set of &lt;code&gt;Message&lt;&#x2F;code&gt; minus the set of &lt;code&gt;Delivered&lt;&#x2F;code&gt;.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Finally, we set up a state machine telling Alloy when each predicate can be true over time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; traces&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    do_nothing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; send_message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; receive_message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That works pretty well! If you step through the traces here, you can get instances where messages are delivered out of order, never delivered, etc. We can&#x27;t get duplicate messages in this model, but that wouldn&#x27;t be the worst thing in the world to add!&lt;&#x2F;p&gt;
&lt;p&gt;Since this is demonstrating the lack of safety on the network, we can&#x27;t add a check like &quot;all messages are eventually delivered&quot;, but we can make assertions that the individual actions are doing what they say they will. For example, we know that if &lt;code&gt;send_message&lt;&#x2F;code&gt; is true then we will always have exactly one new message and no changes to &lt;code&gt;Delivered&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; SendMessageNextStepHasExactlyOneNewMessage&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always send_message &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies one&lt;&#x2F;span&gt;&lt;span&gt; Message&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; SendMessageDoesNotChangeDelivered&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always send_message &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; Delivered&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Delivered&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I debated including &lt;code&gt;Delivered&#x27; = Delivered&lt;&#x2F;code&gt; above, though… it&#x27;s a property I care about, but it&#x27;s also a frame condition. If you squint and think of this like a unit test, it&#x27;s just testing that the implementation is what I expect. But on the other hand, if Alloy allows express my intent this way, where&#x27;s the problem?&lt;&#x2F;p&gt;
&lt;p&gt;We can make similar assertions about &lt;code&gt;receive_message&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; ReceiveMessageOnlyMarksMessagesAsDelivered&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always receive_message &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies one&lt;&#x2F;span&gt;&lt;span&gt; Delivered&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Delivered&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; ReceiveMessageDoesNotChangeMessages&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always receive_message &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; Message&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(If you&#x27;re typing this in by hand and want to go back to just exploring instances, don&#x27;t forget you can add a &lt;code&gt;run {}&lt;&#x2F;code&gt; block at the end to generate something more explorable!)&lt;&#x2F;p&gt;
&lt;p&gt;So with that, we have a fairly nice skeleton to add more network operations onto. For example, imagine that these messages mutated some state. We can now make sure that the state changes are correct in the face of out-of-order or dropped messages. Give it a try and see if you can extend the model to do that!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>growing hydroponic lettuce</title>
		<published>2023-10-01T00:00:00+00:00</published>
		<updated>2025-12-04T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/hydroponics/" type="text/html"/>
		<id>https://bytes.zone/projects/hydroponics/</id>
		<content type="html">&lt;p&gt;Around October 2023, I started growing hydroponic lettuce in my basement.
I first documented my setup at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;kratky-in-the-basement&#x2F;&quot;&gt;Kratky in the basement&lt;&#x2F;a&gt;, but quickly switched to a four-bucket system since 25 gallons of water was far too heavy.&lt;&#x2F;p&gt;
&lt;p&gt;In 2025, I switched from 5-gallon buckets with 3 plants each to 2-gallon buckets with 2 plants each.
I also switched from an off-the-shelf nutrient to a masterblend nutrient formulated for leafy greens.&lt;&#x2F;p&gt;
&lt;p&gt;The two changes together have been phenomenal!
I&#x27;m now getting around 16oz&#x2F;450g of harvested lettuce per week, with minimal waste when cleaning and preparing.&lt;&#x2F;p&gt;
&lt;p&gt;I never expected it, but I&#x27;m actually ahead on system cost relative to buying the same weight of lettuce!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>tsort</title>
		<published>2023-09-25T00:00:00+00:00</published>
		<updated>2023-09-25T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/tsort/" type="text/html"/>
		<id>https://bytes.zone/posts/tsort/</id>
		<content type="html">&lt;p&gt;On the command line, you can use &lt;code&gt;tsort&lt;&#x2F;code&gt; to sort a graph into a list (a topological ordering.) Say you have this chart showing what you need to get dressed:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;tsort-clothes.png&quot; alt=&quot;A graph of dependency relationships among pieces of clothing. It shows you need an undershirt to put on a shirt, shirt to put on a tie, underwear to put on pants, pants to put on a belt, and socks to put on shoes.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can write all those arrows down in a file as dependencies (in order of &quot;X blocks Y,&quot; for example &quot;Shirt blocks Tie,&quot; &quot;Socks block Shoes.&quot;)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Shirt Tie&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Undershirt Shirt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Shirt Belt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Pants Belt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Underwear Pants&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Socks Shoes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Pants Shoes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you pass that to &lt;code&gt;tsort&lt;&#x2F;code&gt; you get this, one order in which you could put on clothes to get dressed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Socks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Undershirt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Underwear&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Shirt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Pants&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Tie&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Shoes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Belt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It kinda suggests lanes of parallelism, too. For example, you could hypothetically put socks, undershirt, and underwear at the same time. Granted, you&#x27;d have to have some machine like in Wallace and Gromit in &lt;em&gt;The Wrong Trousers&lt;&#x2F;em&gt;, but that turned out so well for them! 😆&lt;&#x2F;p&gt;
&lt;p&gt;On a more serious note, I&#x27;ve used this to order tasks in projects in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;linear.app&quot;&gt;Linear&lt;&#x2F;a&gt;. Combined with blocker relationships, it makes it clear to everyone which tasks should go first.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Trying and Failing to Implement Artificial Ignorance</title>
		<published>2023-09-18T00:00:00+00:00</published>
		<updated>2023-09-18T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/trying-and-failing-to-implement-artificial-ignorance/" type="text/html"/>
		<id>https://bytes.zone/posts/trying-and-failing-to-implement-artificial-ignorance/</id>
		<content type="html">&lt;p&gt;I like the ideas behind &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ranum.com&#x2F;security&#x2F;computer_security&#x2F;papers&#x2F;ai&#x2F;index.html&quot;&gt;artificial ignorance&lt;&#x2F;a&gt;. Summed up:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;You can&#x27;t know all the &quot;bad&quot; events that can happen in a system. Modern attackers have basically unlimited kinds of attacks.&lt;&#x2F;li&gt;
&lt;li&gt;You &lt;em&gt;can&lt;&#x2F;em&gt; know all the unexceptional, boring events that could happen. That list might go on for a long time, but it&#x27;ll end at &lt;em&gt;some&lt;&#x2F;em&gt; point.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;System logs tend to contain all the events, so removing the unexceptional events should leave you with just the &quot;interesting&quot; ones. &lt;em&gt;Hypothetically&lt;&#x2F;em&gt; you could make yourself a big regex and then use &lt;code&gt;grep&lt;&#x2F;code&gt; and other shell tools to look over the logs and email yourself interesting events. This won&#x27;t catch just security events, either: you&#x27;ll get warnings about bad configurations, disk space, etc. You&#x27;ll refine this over time, too—new unexceptional events will happen over time and you’ll add them to the list. Doing this means you’ll only ever see novel things you need to deal with.&lt;&#x2F;p&gt;
&lt;p&gt;I find this idea compelling. It seems analogous to running a linter on code: you get things you would not have caught otherwise that might have caused problems later. Saves time and stress, right?&lt;&#x2F;p&gt;
&lt;p&gt;Except, when I tried to apply this I ran into problems. Basically, the sticking point shows up when creating the list: it grows and grows and ends up too long to manage. Having to create and maintain a list of all the things I don&#x27;t care about makes for too much work, even after turning down log levels wherever I could. Over the past couple of months, I&#x27;ve put probably 5 or 6 hours into sitting down with &lt;code&gt;journalctl&lt;&#x2F;code&gt; dumps and writing regex. Too much! I give up!&lt;&#x2F;p&gt;
&lt;p&gt;I can&#x27;t seem to shake the idea, though… I just like the payoff too much!&lt;&#x2F;p&gt;
&lt;p&gt;That time cost has to come down, though… maybe approaching the task a different way? Maybe it would help to take this service-by-service or day-by-day instead of looking at a whole system for a whole month? Or maybe I just need to accept that some services will always produce an unreasonable amount of logs (looking at you, Gitea) and give up on them totally? Surely I can find a way forward!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>crunch_str</title>
		<published>2023-09-11T00:00:00+00:00</published>
		<updated>2023-09-11T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/crunch-str/" type="text/html"/>
		<id>https://bytes.zone/posts/crunch-str/</id>
		<content type="html">&lt;p&gt;When writing &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;crunch-str&#x2F;@content&#x2F;montage.md&quot;&gt;montage&lt;&#x2F;a&gt; I wanted to have a little view of my current task up in the task bar with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;xbarapp.com&#x2F;&quot;&gt;xbar&lt;&#x2F;a&gt;. The GraphQL API made quick work of that—I just asked for the current session and printed it to stdout. Xbar can pick that up and display it, no problem. It looks like this right now as I write this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;montage-xbar.png&quot; alt=&quot;a macOS status bar item saying &amp;quot;write about crunch_str in Montage&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s all well and good, but sometimes my sessions have longer names like &quot;scrub active projects and promote inactive ones&quot; or even longer for tickets at work. Then it takes up way too much room on the screen and pushes other stuff I want in the status bar off the screen. That&#x27;s annoying! I want it to use, like… 40–50 characters, &lt;em&gt;max&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;At this point a reasonable person would just truncate the session name to the first 40 characters or whatever and add an ellipsis. But I don&#x27;t want to be reasonable! I want this to be offbeat and fun!&lt;&#x2F;p&gt;
&lt;p&gt;I got to thinking about more fun ways to shorten these strings. It&#x27;s safe to assume I &lt;em&gt;kinda&lt;&#x2F;em&gt; know what the task says, so could I make the task shorter by removing letters from the string that weren&#x27;t essential to understanding? What if…&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;if I know a shorter version of a word, I just substitute it. For example, I can use &quot;&amp;amp;&quot; instead of &quot;and&quot; or &quot;7&quot; instead of &quot;seven.&quot; Since this is for me, text speak is also fine: &quot;u&quot; for &quot;you&quot;, &quot;y&quot; for &quot;why&quot;, etc.&lt;&#x2F;li&gt;
&lt;li&gt;if a word has double letters, remove &#x27;em! &quot;Occurrence&quot; becomes &quot;ocurence.&quot;&lt;&#x2F;li&gt;
&lt;li&gt;drop vowels from the middle of words. That transforms &quot;ocurence&quot; into &quot;ocrnc&quot;&lt;&#x2F;li&gt;
&lt;li&gt;drop consonants from the middle of words. &quot;ocrnc&quot; becomes &quot;ornc&quot;, &quot;onc&quot;, &quot;oc&quot; over time&lt;&#x2F;li&gt;
&lt;li&gt;if a word has two letters left, make it acronym-y. &quot;oc&quot; is now &quot;O&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This does make the words unintelligible pretty fast, though, so I shorten longer words first (reasoning that a longer word has more chance to remain legible if shortened.)&lt;&#x2F;p&gt;
&lt;p&gt;This works pretty well! There&#x27;s the odd session that gets crunched into nonsense, but clicking the status in my task bar shows the full version of the session so it&#x27;s fine.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s delightful, too! For example, I had a short session today to fix something in my neovim setup. The task &quot;fix alternative test thing for our particular test layout&quot; crunchified down to &quot;fix arntv test thing 4 r ptclr test lout.&quot; Useful as a reminder, but inscrutable if you don&#x27;t know what the session was about in the first place. Just the level of weirdness I was looking for!&lt;&#x2F;p&gt;
&lt;p&gt;All that said, I implemented this whole thing as a Rust crate in the Montage workspace. I can&#x27;t imagine other uses for it, but it&#x27;s reusable if you want to shorten strings in a silly way yourself! You can get it at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;montage&#x2F;src&#x2F;branch&#x2F;main&#x2F;crunch_str&#x2F;src&#x2F;lib.rs&quot;&gt;git.bytes.zone&lt;&#x2F;a&gt;. Like the rest of &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;montage&#x2F;&quot;&gt;montage&lt;&#x2F;a&gt;, it&#x27;s licensed BSD 3-Clause. Have fun!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>once p is true, it&#x27;s always true</title>
		<published>2023-08-28T00:00:00+00:00</published>
		<updated>2023-08-28T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/once-p-is-true-its-always-true/" type="text/html"/>
		<id>https://bytes.zone/posts/once-p-is-true-its-always-true/</id>
		<content type="html">&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;learning-alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;, to say &quot;once this is true, it&#x27;s always true&quot;, you say:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;always (p &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; always p)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is true because &lt;code&gt;always&lt;&#x2F;code&gt; means &quot;in all states starting from this one.&quot; So, the outer &quot;always&quot; means that the condition is always true starting from the initial time. Once &lt;code&gt;p&lt;&#x2F;code&gt; is true, the &lt;code&gt;implies&lt;&#x2F;code&gt; says that it will always remain true.&lt;&#x2F;p&gt;
&lt;p&gt;If you did just &lt;code&gt;p implies always p&lt;&#x2F;code&gt; without the wrapper, that would be saying &quot;if &lt;code&gt;p&lt;&#x2F;code&gt; is true &lt;em&gt;at the initial time&lt;&#x2F;em&gt; then &lt;code&gt;p&lt;&#x2F;code&gt; will always be true.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Source: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.discourse.group&#x2F;t&#x2F;how-do-you-say-once-this-is-true-its-always-true&#x2F;294&quot;&gt;Alcino Cunha&#x27;s answer to my question about this on the Alloy discourse instance&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>montage</title>
		<published>2023-08-21T00:00:00+00:00</published>
		<updated>2023-08-21T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/montage/" type="text/html"/>
		<id>https://bytes.zone/posts/montage/</id>
		<content type="html">&lt;p&gt;I&#x27;ve liked the concept of the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;francescocirillo.com&#x2F;products&#x2F;the-pomodoro-technique&quot;&gt;Pomodoro technique&lt;&#x2F;a&gt; (basically 25 minute work and 5 minutes rest on a loop through the day) for a long time. I get a lot out of it! Problem is, I&#x27;d like to use an app for this but nothing I&#x27;ve tried has been &quot;sticky&quot; enough for me to use long-term… they all have some drawback or other I don&#x27;t want to live with. So like any good software person, I made my own! Long story short, you can get it at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;montage&quot;&gt;git.bytes.zone&#x2F;brian&#x2F;montage&lt;&#x2F;a&gt;. Here&#x27;s what makes it different:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Other apps I&#x27;ve used assume they can take over for your todo list or daily log. Montage only asks that I label my sessions for status and reporting.&lt;&#x2F;li&gt;
&lt;li&gt;Other apps are &quot;polite&quot; with easily-ignorable chimes. Montage gets in my face by yelling at me with the system text-to-speech commands every two seconds when the timer is up. I showed this to a colleague, who described it as &quot;dystopian&quot;, but it&#x27;s the dystopia I chose!&lt;&#x2F;li&gt;
&lt;li&gt;Most other apps I&#x27;ve used don&#x27;t expose a nice API for session control or reporting, but Montage is built around &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;montage&#x2F;src&#x2F;commit&#x2F;dc8749ef12604347d9e57a348e8fb8c97cef9e53&#x2F;montage_client&#x2F;schema.graphql&quot;&gt;a GraphQL API&lt;&#x2F;a&gt; and uses that for everything—there&#x27;s nothing the CLI does that you can&#x27;t get out of it as JSON.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;By exposing a proper API, I was also able to build an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.omnigroup.com&#x2F;omnifocus&quot;&gt;OmniFocus&lt;&#x2F;a&gt; extension so that I can just click &quot;start task&quot; in the app to start something. I was also able to build an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;xbarapp.com&#x2F;&quot;&gt;xbar&lt;&#x2F;a&gt; extension that shows my current task (or break) in the status bar. I get reports with a purpose-made command in the CLI that produces Markdown for me to copy into &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;obsidian.md&#x2F;&quot;&gt;Obsidian&lt;&#x2F;a&gt;. But again, it&#x27;s just talking to the API so I have the flexibility to produce information about my workdays in any format I want.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m pretty happy with Montage so far, and I also have plenty of ideas for more things it should do! For example, it doesn&#x27;t have any opinion on how long you should work before being reminded to take a break. I&#x27;m planning to build that in as a configurable thing in &quot;vex&quot; (the subcommand that talks to me with TTS.) I&#x27;d also like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.raycast.com&#x2F;&quot;&gt;Raycast&lt;&#x2F;a&gt; and Obsidian extensions. At some point it may also make sense to build a GUI or an app, but for now I&#x27;m not too bothered by occasionally having to open up the SQLite database for some manual changes.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, like it says in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;montage&#x2F;src&#x2F;branch&#x2F;main&#x2F;README.md&quot;&gt;the README&lt;&#x2F;a&gt;, go ahead and give this a shot if it seems interesting to you and you&#x27;re OK with being called &quot;Brian&quot; by a computer every 25 minutes!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 13</title>
		<published>2023-08-14T00:00:00+00:00</published>
		<updated>2023-08-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-013/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-013/</id>
		<content type="html">&lt;p&gt;Time passes (almost a month this time) and I&#x27;m back. Enough time has passed that it might make sense to start over from the beginning, but this time I actually have an idea of the product that I want to make: I noticed that in almost every instance where formal methods come up online, there is at least a subtext of &quot;but how do I actually get started in this?&quot; I think I could help there!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Basically, I have noticed that there&#x27;s a sweet spot of formal methods when used as documentation. You don&#x27;t even have to use software for this; you can write a decision table or decision tree on a whiteboard and instantly have something useful for a team to talk about.&lt;&#x2F;p&gt;
&lt;p&gt;This is pretty off-script from what I decided earlier, so I think I want to find a little justification in the real world. Let&#x27;s just root around in Hacker News comments for a bit, since that&#x27;s attracts a pretty wide slice of tech folks.&lt;&#x2F;p&gt;
&lt;p&gt;Some quotes:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;I see lots of value in adopting [TLA+ and Alloy], but the dsl&#x27;s that they use are inscrutable to me. I&#x27;m sure that after some hands-on time I&#x27;d understand them well enough, but having a hard time figuring out how to get that ball rolling.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;It looks pretty impressive, but I’m struggling to find the practical problem it solves. Lots of jargon, eg “constraint language for modelling data structures”. How will that practically help making development easier &#x2F; better &#x2F; more secure?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=20012979&quot;&gt;Some answers to that question by Hillel Wayne&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;hwayne&#x2F;2619df2dd7055a57de96f8769c37fca6&quot;&gt;a gist of an access control model&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;I tried and failed to learn Alloy, the documentation is too mathematically dense for me, even though I suspect the concepts aren’t difficult for a working programmer. Any tips on prerequisite knowledge are appreciated.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Every time I see some TLA+ related article, I read it and then spend an hour convincing myself that I don&#x27;t have time to learn it right now, even though I really want to. That convincing usually comes in the form of showing that the gains are not worth the effort for the software field that I am in. Can someone change my mind on this?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Response to this: &quot;I&#x27;d recommend design by contract as a lighter way of verification.&quot; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=19661809&quot;&gt;The whole thread&lt;&#x2F;a&gt; here is also pretty good with people recommending things, including this great summary of the benefits:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Formal specifications themselves have a few benefits. The first is they force you to make everything explicit that&#x27;s usually a hidden assumption. From there, they might check the consistency of your system. From there, you might analyze them or generate code from the specs. Spotting hidden or ambiguous assumptions plus detecting interface errors were main benefits in industrial projects going back decades.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I love the concept of formal verification, but translating my algorithms into yet another DSL and hoping I get that right seems like a waste of time. I&#x27;d rather simulate the algorithm with my brain.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Can someone who has learned TLA+ comment on how useful it is has been for you?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=19634915&quot;&gt;A story from AWS about saving a ton of time by using TLA+.&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Oh hey, Hillel Wayne&#x27;s &quot;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hillelwayne.com&#x2F;post&#x2F;using-formal-methods&#x2F;&quot;&gt;Using Formal Methods at Work&lt;&#x2F;a&gt;&quot; has documenting an existing system as one of the top-level good ideas. Neato.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;My mind works best with examples, I was about to ask here when I stumbled upon it on the TLA site.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;(In support of documentation—aka examples—as a sweet spot.)&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Simply put, there isn&#x27;t any time to do any of this.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Formal methods can help you come up with a specification (i.e. deciding what the program should do).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Hmm:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Formal specifications are the anthesis to agile thinking, our business folks change demands all the time, expecting them to stick to some up front spec that can be proven is hilariously unlikely. This also ignore the need to continuously update and extend software: it&#x27;s not just a one time development that ends. We are given too little budget as it is and have to make do with too few people and too little time to try to complete things that are constantly changing. You can argue a formal spec makes things better, I can argue not one of our VPs would even understand the concept much less pay for it. Sure that could be fixed by replacing all of our VPs, but unlikely, despite my desiring it.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I don&#x27;t feel like I have as strong a conclusion to this as I&#x27;d like, but I do have some observations after reading the discussions I could find with broad search terms:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Everyone seems to have a different idea of what &quot;formal methods&quot; means, and a different idea about whether they&#x27;re easy&#x2F;hard, quick&#x2F;time consuming, etc. It would make sense for this to be because &quot;formal methods&quot; is an umbrella term for a lot of stuff. For these people, I&#x27;d want to recommend &quot;lightweight&quot; formal methods—and that&#x27;s what I want to teach anyway, not being so mathematically inclined as to teach proofs.&lt;&#x2F;li&gt;
&lt;li&gt;I forgot how mean-spirited Hacker News commenters could be, especially when they&#x27;re trying to build clout without having to engage with the ideas in the things they&#x27;re commenting on. &lt;em&gt;That said&lt;&#x2F;em&gt;, people writing solely from their biases and preconceptions gives me a fairly clear window into their biases and preconceptions. Helpful!&lt;&#x2F;li&gt;
&lt;li&gt;It seems like &quot;documentation&quot; might not be a good way to think about this… maybe it&#x27;s more &quot;build one to throw away&quot; or some other concept? I guess I&#x27;m coming to &quot;documentation&quot; from &quot;if you have thought through something thoroughly, you should be able to explain it to someone else.&quot; If that explanation takes the form of persistent docs, great, but I guess the important thing is the thought process. That&#x27;s what lightweight formal methods are great for!&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;No real conclusion here; I&#x27;m going to have to have a think about what&#x27;s best to do going forward.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>moving genrules to library rules</title>
		<published>2023-08-14T00:00:00+00:00</published>
		<updated>2023-08-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/moving-genrules-to-library-rules/" type="text/html"/>
		<id>https://bytes.zone/posts/moving-genrules-to-library-rules/</id>
		<content type="html">&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;buck2-basics&#x2F;&quot;&gt;buck2 basics&lt;&#x2F;a&gt;, we made two rules:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;some-target&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;target-file&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  cmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;echo &amp;#39;Hello, World!&amp;#39; &amp;gt; $OUT&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;yelling-target&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;target-file&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  cmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;tr &amp;#39;[:lower:]&amp;#39; &amp;#39;[:upper:]&amp;#39; &amp;lt; $(location :some-target) &amp;gt; $OUT&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s lift these to named rules so that we have a nicer API and don&#x27;t mix the commands with the parameters. Let&#x27;s make a couple rules to &lt;code&gt;say&lt;&#x2F;code&gt; and &lt;code&gt;yell&lt;&#x2F;code&gt; (to implement &lt;code&gt;some-target&lt;&#x2F;code&gt; and &lt;code&gt;yelling-target&lt;&#x2F;code&gt;, respectively.) Open a new file &lt;code&gt;talking.bzl&lt;&#x2F;code&gt; and put this in it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; _say_impl&lt;&#x2F;span&gt;&lt;span&gt;(ctx:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;context&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; [DefaultInfo()]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;say&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; rule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    impl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; _say_impl,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    attrs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;message&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: attrs.string(),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;out&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: attrs.string(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;out&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This won&#x27;t do anything yet, but it&#x27;s about the minimum possible rule that lets us do this back in &lt;code&gt;BUCK&lt;&#x2F;code&gt;. I&#x27;ve rendered this as a diff so you can see the before and after:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+load(&amp;quot;:talking.bzl&amp;quot;, &amp;quot;say&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-  name = &amp;quot;some-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-  out = &amp;quot;target-file&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-  cmd = &amp;quot;echo &amp;#39;Hello, World!&amp;#39; &amp;gt; $OUT&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+say(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  name = &amp;quot;some-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  message = &amp;quot;Hello, World!&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   name = &amp;quot;yelling-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   out = &amp;quot;target-file&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   cmd = &amp;quot;tr &amp;#39;[:lower:]&amp;#39; &amp;#39;[:upper:]&amp;#39; &amp;lt; $(location :some-target) &amp;gt; $OUT&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we have &lt;code&gt;:some-target&lt;&#x2F;code&gt; redefined as a call to &lt;code&gt;say&lt;&#x2F;code&gt;, so let&#x27;s implement it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; def _say_impl(ctx: &amp;quot;context&amp;quot;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+    out = ctx.actions.declare_output(ctx.attrs.out)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+    ctx.actions.write(out, ctx.attrs.message)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-    return [DefaultInfo()]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+    return [DefaultInfo(default_output = out)]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; say = rule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     impl = _say_impl,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     attrs = {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         &amp;quot;message&amp;quot;: attrs.string(),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         &amp;quot;out&amp;quot;: attrs.string(default = &amp;quot;out&amp;quot;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;OK! Here&#x27;s what&#x27;s happening there:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we&#x27;re declaring an output file named whatever we passed in in &lt;code&gt;ctx.attrs.out&lt;&#x2F;code&gt;. We didn&#x27;t specify that in &lt;code&gt;BUCK&lt;&#x2F;code&gt; so it&#x27;s the default value, which we called &quot;out&quot;. Importantly, whenever we declare an output we have to &quot;bind&quot; it by using it in an action before the end of the implementation.&lt;&#x2F;li&gt;
&lt;li&gt;So, next, we write the message to that out file. That binds the output.&lt;&#x2F;li&gt;
&lt;li&gt;Then finally, we say that that output is our default output.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;At this point, if you run &lt;code&gt;buck2 build &#x2F;&#x2F;:yelling-target&lt;&#x2F;code&gt; it&#x27;ll produce the same output. We changed how we&#x27;re building the file, but the build graph as a whole produces the same thing. I really like this property; it means I can improve parts of the build graph in isolation without worrying too much about upstream or downstream dependencies.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, let&#x27;s do the same thing to &lt;code&gt;:yelling-target&lt;&#x2F;code&gt; now by making a &lt;code&gt;yell&lt;&#x2F;code&gt; rule:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; _yell_impl&lt;&#x2F;span&gt;&lt;span&gt;(ctx:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;context&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; ctx.actions.declare_output(ctx.attrs.out)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ctx.actions.run(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;bash&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;-c&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;tr &amp;#39;[:lower:]&amp;#39; &amp;#39;[:upper:]&amp;#39; &amp;lt; $1 &amp;gt; $2&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;--&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ctx.attrs.src,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            out.as_output(),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        category&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;yell&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; [DefaultInfo(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;default_output&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; out)]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;yell&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; rule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  impl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; _yell_impl,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  attrs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;      &amp;quot;src&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: attrs.source(),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;      &amp;quot;out&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: attrs.string(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;out&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We have some more stuff happening here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We&#x27;re calling a &lt;code&gt;bash&lt;&#x2F;code&gt; script to get the shell redirection working correctly. I write a lot of little bash scripts like this when making Buck rules. There are probably better ways to do this, but it works fine for what we&#x27;re doing here.&lt;&#x2F;li&gt;
&lt;li&gt;When we pass &lt;code&gt;out&lt;&#x2F;code&gt; to the args of our script, we call &lt;code&gt;.as_output()&lt;&#x2F;code&gt; on it. That lets Buck know that we&#x27;re binding the output in that command.&lt;&#x2F;li&gt;
&lt;li&gt;We&#x27;re requiring &lt;code&gt;attrs.source()&lt;&#x2F;code&gt; in &lt;code&gt;src&lt;&#x2F;code&gt;, which tells Buck that we&#x27;re expecting some already-existing input file.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Now in &lt;code&gt;BUCK&lt;&#x2F;code&gt;, we can modify our rules like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-load(&amp;quot;:talking.bzl&amp;quot;, &amp;quot;say&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+load(&amp;quot;:talking.bzl&amp;quot;, &amp;quot;say&amp;quot;, &amp;quot;yell&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; say(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   name = &amp;quot;some-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   message = &amp;quot;Hello, World!&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-  name = &amp;quot;yelling-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-  out = &amp;quot;target-file&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-  cmd = &amp;quot;tr &amp;#39;[:lower:]&amp;#39; &amp;#39;[:upper:]&amp;#39; &amp;lt; $(location :some-target) &amp;gt; $OUT&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FDAEB7;background-color: #86181D;&quot;&gt;-)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+yell(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  name = &amp;quot;yelling-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  src = &amp;quot;:some-target&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since &lt;code&gt;src&lt;&#x2F;code&gt; is a &lt;code&gt;source&lt;&#x2F;code&gt;, we provide the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buck2.build&#x2F;docs&#x2F;concepts&#x2F;target_pattern&#x2F;&quot;&gt;target pattern&lt;&#x2F;a&gt; of the input file we want. That means our build graph is unchanged once again!&lt;&#x2F;p&gt;
&lt;p&gt;And we&#x27;re done! Now we can call &lt;code&gt;buck2 build &#x2F;&#x2F;:yelling-target&lt;&#x2F;code&gt;, same as before. Tada!&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, that&#x27;s all I have for Buck stuff—like I said in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;buck2-basics&#x2F;&quot;&gt;buck2 basics&lt;&#x2F;a&gt;, I&#x27;ll be watching the project to see when I can use it again!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>buck2 basics</title>
		<published>2023-08-07T00:00:00+00:00</published>
		<updated>2023-08-07T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/buck2-basics/" type="text/html"/>
		<id>https://bytes.zone/posts/buck2-basics/</id>
		<content type="html">&lt;p&gt;I just finished an experiment with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buck2.build&quot;&gt;Buck 2&lt;&#x2F;a&gt; at work. We didn&#x27;t end up using it, but it was more for organizational reasons than technical ones. I&#x27;ll be watching the project to see if it would make sense to use in the future!&lt;&#x2F;p&gt;
&lt;p&gt;One thing that held me back during the early part of the experiment was not having enough learning material around the basics. That makes sense: it&#x27;s a pretty new project (at least in this iteration) and there are a &lt;em&gt;lot&lt;&#x2F;em&gt; of concepts to learn.&lt;&#x2F;p&gt;
&lt;p&gt;So instead of teaching things from first principles, I want to run head-first into getting something simple set up. We won&#x27;t have any more functionality than we could get with some simple &lt;code&gt;make&lt;&#x2F;code&gt; rules, but since &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;learning-requires-effort&#x2F;&quot;&gt;the person who does the work does the learning&lt;&#x2F;a&gt; I hope this will be helpful! That said, keep in mind that I&#x27;m no expert—just some person who learned enough to get stuff done!&lt;&#x2F;p&gt;
&lt;aside&gt;
&lt;p&gt;I&#x27;m going to assume you have buck2 available on your &lt;code&gt;PATH&lt;&#x2F;code&gt;. If you don&#x27;t, download it from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;facebook&#x2F;buck2&#x2F;releases&#x2F;tag&#x2F;latest&quot;&gt;the &lt;code&gt;latest&lt;&#x2F;code&gt; tag on the facebook&#x2F;buck2 repo&lt;&#x2F;a&gt;. Once you do that, you can run &lt;code&gt;buck2 init --git&lt;&#x2F;code&gt; to get a basic project set up for the rest of this post.&lt;&#x2F;p&gt;
&lt;&#x2F;aside&gt;
&lt;h2 id=&quot;the-simplest-possible-rule&quot;&gt;The Simplest Possible Rule&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ll start off with the simplest possible thing: a file that says &quot;Hello, World!&quot; We&#x27;ll use a &lt;code&gt;genrule&lt;&#x2F;code&gt; for this, which lets you say what shell commands to run to produce some file. In the root &lt;code&gt;BUCK&lt;&#x2F;code&gt; file, write this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;some-target&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;target-file&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  cmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;echo &amp;#39;Hello, World!&amp;#39; &amp;gt; $OUT&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That configures a target named &lt;code&gt;some-target&lt;&#x2F;code&gt; that you can build with &lt;code&gt;buck2 build &#x2F;&#x2F;:some-target&lt;&#x2F;code&gt;. If you add &lt;code&gt;--show-output&lt;&#x2F;code&gt; to that, it&#x27;ll tell you where the file is, and if you &lt;code&gt;cat buck-out&#x2F;v2&#x2F;gen&#x2F;root&#x2F;213ed1b7ab869379&#x2F;__some-target__&#x2F;out&#x2F;target-file&lt;&#x2F;code&gt; (or whatever path Buck gives you) it out it&#x27;ll say &quot;Hello, World!&quot; First build done. Yay!&lt;&#x2F;p&gt;
&lt;p&gt;So what do all the parts in that rule mean?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;genrule(…)&lt;&#x2F;code&gt; is the rule you&#x27;re calling. I think &quot;gen&quot; here is short for &quot;generic&quot; (or maybe &quot;generate&quot;?) Anyway, it&#x27;s the simplest thing that the build system can possibly do.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;name&lt;&#x2F;code&gt; defines how you&#x27;ll refer to this target from other rules or from the CLI. See &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buck2.build&#x2F;docs&#x2F;concepts&#x2F;target_pattern&#x2F;&quot;&gt;the target pattern docs&lt;&#x2F;a&gt;, which also explain why you refer to this as &lt;code&gt;&#x2F;&#x2F;:some-target&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;out&lt;&#x2F;code&gt; is the file that will be produced by the rule&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cmd&lt;&#x2F;code&gt; is the command that will be run to produce the file. &lt;code&gt;$OUT&lt;&#x2F;code&gt; looks like an environment variable reference, but as far as I can tell Buck is actually interpolating it into the string before calling the command. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buck2.build&#x2F;docs&#x2F;api&#x2F;rules&#x2F;#genrule&quot;&gt;The docs for &lt;code&gt;genrule&lt;&#x2F;code&gt; have some examples of other variables you can use&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;more&quot;&gt;More!&lt;&#x2F;h2&gt;
&lt;p&gt;Next, let&#x27;s chain two rules together. Here&#x27;s another rule in the same file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;genrule(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;yelling-target&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;target-file&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;  cmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;tr &amp;#39;[:lower:]&amp;#39; &amp;#39;[:upper:]&amp;#39; &amp;lt; $(location :some-target) &amp;gt; $OUT&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The new thing here is &lt;code&gt;$(location ...)&lt;&#x2F;code&gt;, which interpolates the location of some the output of the named target into the command line.&lt;&#x2F;p&gt;
&lt;aside&gt;
&lt;p&gt;I had a hard time finding out if there was anything else like &lt;code&gt;location&lt;&#x2F;code&gt; that I could use. It turns out that the call there is actually a lookup for the &quot;location&quot; attribute on the target inside Buck&#x27;s build graph. That means different rules will produce different things! But generally speaking, &lt;code&gt;location&lt;&#x2F;code&gt; will work for files in general and &lt;code&gt;executable&lt;&#x2F;code&gt; will work for files that you need to run.&lt;&#x2F;p&gt;
&lt;&#x2F;aside&gt;
&lt;p&gt;Now if you build everything and go look at the output (using &lt;code&gt;--show-output&lt;&#x2F;code&gt;), you will get a new file that says &quot;HELLO, WORLD!&quot;. If you change the original rule, the new one will get built too.&lt;&#x2F;p&gt;
&lt;p&gt;So with these two rules (plus the thing in &lt;code&gt;toolchain&lt;&#x2F;code&gt;), you can:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;buck2 build &#x2F;&#x2F;...&lt;&#x2F;code&gt; to build both targets&lt;&#x2F;li&gt;
&lt;li&gt;Run &lt;code&gt;buck2 build &#x2F;&#x2F;:some-target&lt;&#x2F;code&gt; to build only the &quot;Hello, World!&quot; file.&lt;&#x2F;li&gt;
&lt;li&gt;Run &lt;code&gt;buck2 build &#x2F;&#x2F;:yelling-target&lt;&#x2F;code&gt; to build the &quot;HELLO, WORLD!&quot; file, which will build &lt;code&gt;:some-target&lt;&#x2F;code&gt; as well, if necessary.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Try changing things around (for example by changing the message) and seeing what happens. You can also run &lt;code&gt;buck clean&lt;&#x2F;code&gt; to remove the targets to get a fresh build.&lt;&#x2F;p&gt;
&lt;p&gt;Next time, we&#x27;ll talk about moving these from &lt;code&gt;genrule&lt;&#x2F;code&gt;s into a library that we can import and call.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Do I want to learn Dafny?</title>
		<published>2023-07-28T00:00:00+00:00</published>
		<updated>2023-07-28T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/dafny-001/" type="text/html"/>
		<id>https://bytes.zone/micro/dafny-001/</id>
		<content type="html">&lt;p&gt;So I recently learned about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dafny.org&#x2F;&quot;&gt;Dafny&lt;&#x2F;a&gt;. From my notes on it, I summarized it as:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;a verified programming language. That is, you can write a program with preconditions, postconditions, proofs, etc and then export that code to C#, Java, JavaScript, and Go (with more languages coming.)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I&#x27;m curious about this! Getting better tools for writing reliable software feels intrinsically good to me. After all, even if I&#x27;m not working on the space shuttle or medical equipment where bugs are life-threatening, they can still be costly and annoying.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I also know that correctness is not the only goal in software engineering. For example: right now, I&#x27;m working on software for work that we hope will be useful to expand into a new market segment (from high schools to upper elementary schools.) At the moment, it&#x27;s less important that the software I&#x27;m writing be &lt;em&gt;correct&lt;&#x2F;em&gt;, and more important that it just &lt;em&gt;exist&lt;&#x2F;em&gt;. We can work out the bugs after we figure out what&#x27;s most valuable to classroom teachers!&lt;&#x2F;p&gt;
&lt;p&gt;But &lt;em&gt;what if it was faster&lt;&#x2F;em&gt; to write correct software? That sure would open up some things for me to explore!&lt;&#x2F;p&gt;
&lt;p&gt;So, maybe it&#x27;s worth looking into Dafny? I see there are some books that have to do with proving theorems generally which may be useful learning… I remember when I&#x27;ve tried other systems like this (notably Coq but also Idris to a lesser extent) I&#x27;ve really been fumbling around in the dark trying to get programs proved. I wonder if having more foundational knowledge would help?&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I think I&#x27;ll give it a go, at least for a bit!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 12</title>
		<published>2023-07-19T00:00:00+00:00</published>
		<updated>2023-07-19T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-012/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-012/</id>
		<content type="html">&lt;p&gt;Ok, it&#x27;s been about ten days since I last worked on this project. In the intervening time I&#x27;ve been working on some custom software to do time tracking in the way I want, so I haven&#x27;t been focusing on this. But, you know what, it&#x27;s Wednesday night and time to party! And by &quot;party&quot; I mean &quot;search the web for people talking about property testing&quot;. SO GRAB YOUR CYBERDECK AND LET&#x27;S HACK ON.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Reddit! There are a few posts on &#x2F;r&#x2F;programming that include &quot;property-based testing&quot;. There are more in the Haskell, Python, and Rust subreddits. One in Ruby, as well. (Hypothesis briefly had a Ruby port. Maybe still has one?)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Oh, before I continue with the bullets, I should say: people call this &quot;property-based testing&quot; or &quot;property testing&quot; or &quot;PBT&quot; or &quot;PBT testing&quot; (real ATM Machine vibes there) or &quot;fuzz testing&quot; (although that&#x27;s &lt;em&gt;technically&lt;&#x2F;em&gt; a different thing.) They also refer to it by library&#x2F;ecosystem where X is one of the libraries that does this. Hypothesis for Python, QuickCheck for Haskell&#x2F;Erlang, Hedgehog for Haskell&#x2F;F#&#x2F;some others, elm-test for Elm, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway if you search &quot;property-based testing forum&quot; or &quot;property testing forum&quot; you get a bunch of intro blog posts and some whitepapers. There&#x27;s also some more relevant results, like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elixirforum.com&#x2F;tag&#x2F;property-testing&quot;&gt;the Elixir subforum dedicated to property testing&lt;&#x2F;a&gt; (which is fairly inactive.)&lt;&#x2F;p&gt;
&lt;p&gt;… searching searching searching forgot to make bullets …&lt;&#x2F;p&gt;
&lt;p&gt;Ok, turns out I can find a few easy-to-find things very easily, but there doesn&#x27;t seem to be much depth behind them. &lt;em&gt;In fact&lt;&#x2F;em&gt;, I&#x27;m finding fewer total discussions than I found with formal methods. I &lt;em&gt;am&lt;&#x2F;em&gt; finding more total blog posts, though, which is not nothing… but it&#x27;s also not something I can use.&lt;&#x2F;p&gt;
&lt;p&gt;Ok, sigh, what to do about it then? Back to OODA, I guess.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observe: what am I feeling, why, and what is my impulse?&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What am I feeling?&lt;&#x2F;strong&gt; &lt;em&gt;Sigh&lt;&#x2F;em&gt;, another &quot;probably not a good idea&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Why?&lt;&#x2F;strong&gt; &quot;property-based testing&quot; seems to be another strikeout, see above.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What is my impulse?&lt;&#x2F;strong&gt; Switch to &quot;team leads&quot; and try this exercise again.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Orient:&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is happening?&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;I spent like 45 minutes finding not very much again.&lt;&#x2F;li&gt;
&lt;li&gt;There&#x27;s no single &quot;community&quot;, just people using the general technique across a bunch of different programming languages.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What does the data say?&lt;&#x2F;strong&gt; The kind of discussion I want to read and participate in doesn&#x27;t exist, at least not in places that are accessible for me. (disclosure: I copied this from last time.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What do I want?&lt;&#x2F;strong&gt; I want a place to get a slightly easier foothold. (disclosure: same.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Decide:&lt;&#x2F;strong&gt; I&#x27;m going to move on to Rust. &quot;Why not team leads&quot; below.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Act:&lt;&#x2F;strong&gt; Going to carve out 25 minutes to try this tomorrow. I bet I can find stuff pretty quickly.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Before I said that I was going to carve out some time to focus on why I was hesitant to pursue Rust as an opportunity. Basically it came down to wanting to pursue techniques over tools… in particular, things that had more staying power than trendiness. But look: pursuing Elm meant that many people in the Elm community knew my name, that I had a successful conference, and eventually that I got a solid job that I&#x27;ve been at for the last 6 years. Even today I keep in touch with a lot of people that I got to know and like through the Elm community. Even though Elm is not something I want to pursue &lt;em&gt;right now&lt;&#x2F;em&gt;, I couldn&#x27;t exactly call any of that unsuccessful. Going somewhere that I don&#x27;t have the same kinds of feelings about but which has similar potential upsides seems like something I can get behind!&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, it seems like pursuing &quot;tech leads&quot; might be more of the same kind of issues that I&#x27;ve seen before. Plus, I feel some fear about giving people bad advice and hurting their careers instead of just their code.&lt;&#x2F;p&gt;
&lt;p&gt;So all that to say that I&#x27;ll be pursuing Rust next! Looking forward to it.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 11</title>
		<published>2023-07-09T00:00:00+00:00</published>
		<updated>2023-07-09T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-011/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-011/</id>
		<content type="html">&lt;p&gt;Before I just jump into finding communities around property testing, I wanted to try and do a cold answer of the launch FTW questions in lesson 4 (which I&#x27;ve been putting off since… June 26th? Shorter than I thought, actually.) Anyway, here they are, along with my answers (cleaned up slightly.)&lt;&#x2F;p&gt;
&lt;p&gt;By the way, if you&#x27;re unaware: property testing is like unit testing but with automatic test case generation. This is a straw man, but in example-based testing of an &quot;add&quot; method you might say &quot;ok, add makes numbers go up&quot; and write down pairs like 1+1, 2+20, 3+1e9. In property testing, you might say &quot;add &lt;em&gt;always&lt;&#x2F;em&gt; makes numbers go up&quot; and tell the system to give you any two numbers, asserting that the result is higher than both of them. Then the property testing system will immediately give you a negative number and you&#x27;ll be like &quot;oh duh.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, the questions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people need to know, but don&#x27;t?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You don&#x27;t have to be a galaxy brain to come up with good properties. Most follow a small number of themes.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people fail to do?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Writing down a description of your system is often enough for finding good properties, and people don&#x27;t really do that. At least in my experience, you get the &quot;blank canvas&quot; experience where you&#x27;re just staring at a screen and saying &quot;uhhhhhh&quot; if you miss this step.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that that people waste time on?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Writing property tests that only exercise like half the possible cases instead of finding ones that are universally true. Leads to a lot of flakes and conditionals in the test. Like other forms of testing, this is a &quot;smell&quot; and can mean you should break up the system under test to make it more testable.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people always screw up?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Other than the above… over-filtering, maybe? Just &quot;too many conditionals&quot; in general.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people complain about all the time?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Property testing requires more and different kinds of effort than example-based unit testing. (They use language like &quot;it&#x27;s harder&quot; but seem to mean &quot;it&#x27;s harder &lt;em&gt;for me, with my current skill set&lt;&#x2F;em&gt;&quot;) People often do not see the benefits, either, because their property tests are flakier &#x27;cause of all the conditionals.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people don&#x27;t understand?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Mathematical properties are often better than stuff you just make up, but the stuff you make up can work unreasonably well too.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one valuable thing that people don&#x27;t have time for?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Sitting down and writing out a full specification of their system, at least semi-formally. That often shows a bunch of useful properties and is helpful regardless, but it involves using a bunch of hard-to-acquire skills and takes quite some time.&lt;&#x2F;p&gt;
&lt;p&gt;Also, verifying that the examples follow a reasonable distribution (e.g. not cutting off all the edge cases.)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people love achieving?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Getting tests failures that make them say &quot;oh, I hadn&#x27;t thought about &lt;em&gt;that&lt;&#x2F;em&gt;.&quot;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people feel great about?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After having a passing test suite, people tend to feel better that the code is more &quot;bulletproof&quot; than other code they&#x27;ve written.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that people consider to be &quot;successful?&quot;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Having working software. &quot;Correct&quot; or &quot;correctness&quot; is often invoked.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that makes them lose money?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Bugs, I guess? Or the costs of having to redo work (although in my own experience that&#x27;s more of a social cost than a monetary one.)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What&#x27;s one thing that makes them earn more money?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Since the audience is mostly programmers, and programmers mostly make money by being employed… I guess the perception of intelligence? You can get a lot more people to listen to you by being perceived as smart. Commands a higher salary, too.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This was difficult for me! It&#x27;s early and I&#x27;m pretty tired and it felt like my thoughts were extra slippery. I&#x27;m glad I put the effort in to tie them down; I see some themes here around accessibility and approach here that could at least be useful products.&lt;&#x2F;p&gt;
&lt;p&gt;I think it might also be worth revisiting this exercise and putting in the effort to answer these questions for Sorbet or Formal Methods. I&#x27;ve been in a (broadly-published) blogging drought recently and it seems like this could generate good ideas for that, too.&lt;&#x2F;p&gt;
&lt;p&gt;Next time I&#x27;m going to go out and if I can find reasonably-sized communities around property testing. I was able to find a few things immediately but I want to dig in more.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 10</title>
		<published>2023-07-06T00:00:00+00:00</published>
		<updated>2023-07-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-010/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-010/</id>
		<content type="html">&lt;p&gt;As promised, it&#x27;s tomorrow and I&#x27;m brainstorming more audiences. In addition to including things where I have an advantage, this time I&#x27;m trying to be as specific as possible without limiting community size. I want to be able to read posts and interact with people regularly, so let&#x27;s say an ideal size would be having a post per day or more.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s my list (I&#x27;ve italicized new things and crossed out ones that I&#x27;ve made more specific:)&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Elm&lt;&#x2F;li&gt;
&lt;li&gt;Rust&lt;&#x2F;li&gt;
&lt;li&gt;Ruby&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Haskell&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;del&gt;frontend developers&lt;&#x2F;del&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;CSS&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;JavaScript&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;TypeScript&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;del&gt;TDD practitioners&lt;&#x2F;del&gt;&lt;&#x2F;li&gt;
&lt;li&gt;unit testing&lt;&#x2F;li&gt;
&lt;li&gt;integration testing&lt;&#x2F;li&gt;
&lt;li&gt;property-based testing&lt;&#x2F;li&gt;
&lt;li&gt;tech&#x2F;team leadership&lt;&#x2F;li&gt;
&lt;li&gt;staff engineering&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Vim&#x2F;Neovim&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And again, let&#x27;s put this into &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elo.bytes.zone&quot;&gt;ELO anything&lt;&#x2F;a&gt;…&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Rank&lt;&#x2F;th&gt;&lt;th&gt;Item&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Rating&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Difference from Last&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;td&gt;property testing&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1401&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;td&gt;Rust&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1369&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;32&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;3&lt;&#x2F;td&gt;&lt;td&gt;tech&#x2F;team leadership&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1359&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;4&lt;&#x2F;td&gt;&lt;td&gt;Ruby&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1293&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;66&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;5&lt;&#x2F;td&gt;&lt;td&gt;staff engineering&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1275&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;6&lt;&#x2F;td&gt;&lt;td&gt;unit testing&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1223&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;52&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;7&lt;&#x2F;td&gt;&lt;td&gt;CSS&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1220&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;8&lt;&#x2F;td&gt;&lt;td&gt;Haskell&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1199&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;21&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;9&lt;&#x2F;td&gt;&lt;td&gt;Elm&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1164&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;35&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;10&lt;&#x2F;td&gt;&lt;td&gt;Vim&#x2F;Neovim&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1117&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;47&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;11&lt;&#x2F;td&gt;&lt;td&gt;TypeScript&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1088&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;29&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;12&lt;&#x2F;td&gt;&lt;td&gt;integration testing&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1075&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;13&lt;&#x2F;td&gt;&lt;td&gt;JavaScript&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;906&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;169&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Overall, I have 3 reasonable top things. There&#x27;s not a huge difference between them, but two of them are techniques. I&#x27;ve had trouble with that so far!&lt;&#x2F;p&gt;
&lt;p&gt;But, to be honest, I really love property testing—it ticks the same boxes for me that lightweight formal methods does, but with the benefit that it refines to actually testing code. Point is: I&#x27;m enthusiastic about this! And, as another bonus, I&#x27;ve taught the basics to enough people to know what they struggle with. I think my own enthusiasm is a big bonus here: I&#x27;m going to have a harder time making this work sustainably without that seed. (However, I&#x27;m also aware that I don&#x27;t want to rely on riding a happy feeling. Work can be hard and I won&#x27;t always feel this way!)&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, Rust might make a lot of sense. There&#x27;s an enthusiastic community and the people seem pretty reasonable. I feel a hesitation to dive in, though, and I&#x27;m not sure where it comes from. I think I&#x27;m going to leave that on the shelf for now, though, and focus on property testing.&lt;&#x2F;p&gt;
&lt;p&gt;So, next steps… I could try to find communities to look at. A brief search shoes that there&#x27;s some in Elixir, some in Python (with Hypothesis,) and I know that there&#x27;s some discussion on this in other FP languages. There are also some classic blog posts that I could stand to re-read. But first, I want to see if I can do the actual Launch FTW lesson cold. (Yes, finally getting back to that!) That&#x27;ll be next time!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 9</title>
		<published>2023-07-05T00:00:00+00:00</published>
		<updated>2023-07-05T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-009/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-009/</id>
		<content type="html">&lt;p&gt;I&#x27;m currently in a &quot;business hack and chat&quot; meeting with some Recurse Center alumni! I&#x27;m gonna spend 45 minutes looking for people chatting about &lt;em&gt;improving&lt;&#x2F;em&gt; with formal methods (instead of just using the term colloquially or publishing papers about their use.) My hit list: look around on subreddits, then look in some tool-specific forums to see if there are newbies there. I&#x27;m thinking Alloy and TLA+ to start, but maybe I&#x27;ll find more around the edges.&lt;&#x2F;p&gt;
&lt;p&gt;(2 minutes in) OK, let&#x27;s try &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;&quot;&gt;&#x2F;r&#x2F;formalmethods&lt;&#x2F;a&gt; first. That seems broad. … Well, maybe overbroad. There appear to be only about 20 posts in the last year, and quite a few of them seem to be promotion-focused instead of asking questions. Some learning posts show up, though: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;comments&#x2F;wafwjn&#x2F;interested_in_pursuing_a_phd_in_formal_methods&#x2F;&quot;&gt;someone is interested in pursuing a PhD in formal methods&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;comments&#x2F;z6qa2c&#x2F;help_i_cant_figure_out_a_formal_method_for_my&#x2F;&quot;&gt;someone who is having trouble with a graduate program&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;comments&#x2F;w1zapf&#x2F;review_of_model&#x2F;&quot;&gt;someone asking for review of an Alloy model&lt;&#x2F;a&gt; (but they&#x27;re asking to ask, so there are no actual replies in the last year), &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;comments&#x2F;viw53c&#x2F;interviewing_formal_method_practitioner&#x2F;&quot;&gt;someone trying to find practitioners to interview&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;comments&#x2F;vhrw1u&#x2F;pragmatic_formal_modeling&#x2F;&quot;&gt;someone who has made a &quot;pragmatic guide to formal modeling&quot;&lt;&#x2F;a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;formalmethods&#x2F;comments&#x2F;uuqg19&#x2F;popularizing_formal_methods&#x2F;&quot;&gt;someone wondering how to make FM more popular&lt;&#x2F;a&gt;. Most of these seem like OK threads to try and read to get a sense of people&#x27;s struggles, but overall it doesn&#x27;t seem like this is a place I could get involved in community—it&#x27;s just too slow!&lt;&#x2F;p&gt;
&lt;p&gt;(10 minutes in) However, there was a link to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;reddit.com&#x2F;r&#x2F;systems_engineering&quot;&gt;&#x2F;r&#x2F;systems_engineering&lt;&#x2F;a&gt; that seems much larger and some of the posts use &quot;formal&quot; in a way that seems promising. But once I looked at it, it seems like this is a large-scale traditional engineering thing. Lots of ™s and ®s after the official pages of these products. There&#x27;s definitely money there waiting to be spent, but it&#x27;s not something that I have any particular skill or advantage in.&lt;&#x2F;p&gt;
&lt;p&gt;(15 minutes in) Looked at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;reddit.com&#x2F;r&#x2F;tlaplus&quot;&gt;&#x2F;r&#x2F;tlaplus&lt;&#x2F;a&gt; briefly. It wasn&#x27;t super promising. Seems low-traffic again. Reddit asked me if the subreddit had profanity in its name. I said &quot;no&quot; and then it asked me if there&#x27;s profanity in the posts. I mean, I don&#x27;t know man… I don&#x27;t &lt;em&gt;not&lt;&#x2F;em&gt; swear when I&#x27;m using TLA+, great though it is.&lt;&#x2F;p&gt;
&lt;p&gt;(26 minutes in) It&#x27;s probably about time to go look at tool-specific forums. I know about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.discourse.group&#x2F;&quot;&gt;the Alloy discourse&lt;&#x2F;a&gt;, which is helpful but fairly slow-paced. I snagged some threads to read later there, though. There&#x27;s also a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;groups.google.com&#x2F;g&#x2F;tlaplus&quot;&gt;TLA+ Google Group&lt;&#x2F;a&gt;. (Side note: TLA+ is probably the best-known formal methods tool in my circles, so I&#x27;d probably end up using a lot of it… is that where I want to take this business? I&#x27;m not sure I do!)&lt;&#x2F;p&gt;
&lt;p&gt;OK, 30 minutes in. Time for some OODA:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observe: what am I feeling, why, and what is my impulse?&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What am I feeling?&lt;&#x2F;strong&gt; I&#x27;m feeling discouraged and a little stressed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Why?&lt;&#x2F;strong&gt; Discouraged because I can&#x27;t find the kinds of things I&#x27;m looking for, stressed because OH NO this is the second time what if I can&#x27;t EVER FIND ANYTHING???? Drama drama drama drama. (Only feeling this a little, fortunately.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What is my impulse?&lt;&#x2F;strong&gt; Call this another &quot;no&quot; and switch audiences again.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Orient:&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is happening?&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;I spent 45 minutes or so finding not very much in the common locations, although I didn&#x27;t look extensively on StackOverflow.&lt;&#x2F;li&gt;
&lt;li&gt;The folks I emailed say there&#x27;s no big mass of FM folks hanging out online (except on LinkedIn, where folks have landed after Twitter.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What does the data say?&lt;&#x2F;strong&gt; The kind of discussion I want to read and participate in doesn&#x27;t exist, at least not in places that are accessible for me.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What do I want?&lt;&#x2F;strong&gt; I want a place to get a slightly easier foothold.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Decide:&lt;&#x2F;strong&gt; Sounds like it&#x27;s time to broaden my search in one of a couple ways:
&lt;ul&gt;
&lt;li&gt;I could go back to Ruby, but focus on Ruby for itself now instead of Ruby for Sorbet&#x27;s sake.&lt;&#x2F;li&gt;
&lt;li&gt;I could do the next item on &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;micro&#x2F;launchftw-003&#x2F;&quot;&gt;my possibilities list&lt;&#x2F;a&gt;, which is something in frontend. Maybe CSS?&lt;&#x2F;li&gt;
&lt;li&gt;I could try and come up with more possibilities and analyze those.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Act:&lt;&#x2F;strong&gt; At the risk of overanalysis, I think I&#x27;m going to try and brainstorm a few more possibilities tomorrow.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 8</title>
		<published>2023-07-04T00:00:00+00:00</published>
		<updated>2023-07-04T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-008/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-008/</id>
		<content type="html">&lt;p&gt;After asking around, it turns out that people &quot;in&quot; the formal methods community aren&#x27;t really that numerous and don&#x27;t seem to hang out a lot online, or they hang out in tool-specific places. I get the feeling, though, that that&#x27;s just where the experts hang out… what about the beginners? People struggling to get their heads around what they want to do? Where can they be found? Same question about people looking to hire or buy services—is that LinkedIn? Somewhere else?&lt;&#x2F;p&gt;
&lt;p&gt;I feel like I&#x27;m asking the same questions I was asking when looking for Sorbet chat, but with more of a sense that &lt;em&gt;something&lt;&#x2F;em&gt; is out there for me to find. I&#x27;ve seen people mentioning formal methods online &lt;em&gt;far&lt;&#x2F;em&gt; more than I&#x27;ve seen them mentioning Sorbet, so those folks are surely out there, right?&lt;&#x2F;p&gt;
&lt;p&gt;Some things I&#x27;ve looked at to try and verify that I&#x27;m not in a bubble again:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;search on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lobste.rs&quot;&gt;Lobsters&lt;&#x2F;a&gt; for &quot;formal methods.&quot; There&#x27;s some discussion, but as you&#x27;d expect it&#x27;s mostly around the links that people submit.&lt;&#x2F;li&gt;
&lt;li&gt;searched for &quot;formal methods&quot; on LinkedIn for the same. Got a few job ads and some spammy results. I know there&#x27;s discussion there, but I suspect you have to be connected to the right people (and I&#x27;m not, at least on that platform.) Searching &lt;code&gt;#formalverification&lt;&#x2F;code&gt; gets a few better results, but it&#x27;s not a lot of discussion.&lt;&#x2F;li&gt;
&lt;li&gt;searched for &quot;formal methods&quot; on Hacker News. Lots of people using the term in different and incompatible ways, which implies that they are probably outside the space or are using it colloquially (which seems common for Hacker News.)&lt;&#x2F;li&gt;
&lt;li&gt;searched for &quot;formal methods&quot; on one of the watering holes I found in Ruby-land. There are people actually talking about this! Interesting! Mostly, though, they&#x27;re repeating the claim that Ruby is impossible to analyze because of how dynamic&#x2F;metaprogrammy it is. Common refrain.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This isn&#x27;t a &lt;em&gt;lot&lt;&#x2F;em&gt; to go on, but I haven&#x27;t looked at Reddit or tool-specific forums like Dafny, Alloy, etc yet. I am now remembering how many LaTeX-formatted papers you need to go through to make progress in this space, though, which indicates that there is a lot of academic work. I&#x27;m not in those spaces, though, and I suspect I&#x27;d struggle to break in.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s all the time I have for today, so we&#x27;ll have to pick this up next time.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 7</title>
		<published>2023-07-02T00:00:00+00:00</published>
		<updated>2023-07-02T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-007/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-007/</id>
		<content type="html">&lt;p&gt;I&#x27;ve felt a little stuck for the past couple of days. There&#x27;s a couple parts to this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;(Minor) I intended to get up early to work on this, as I usually do, but had a hard time waking up and ended up falling back to sleep and missing the chance.&lt;&#x2F;li&gt;
&lt;li&gt;(Major) When I &lt;em&gt;did&lt;&#x2F;em&gt; have the opportunity, I haven&#x27;t felt motivated by Ruby or Sorbet stuff. In fact, looking around it seems like Sorbet is still fairly niche. From community discussions I&#x27;ve looked at, it seems like most people are bouncing off Sorbet (and RBS too) saying things like &quot;it&#x27;s never good when there&#x27;s daylight in between the things your type system can express and the things your language can express.&quot; And, I mean, that&#x27;s fair! It&#x27;s also overcome-able, but do I really want to fight an uphill battle there?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Basically, I feel uncertain whether the audience I had in mind (Rubyists interested in adding static typing for additional safety) actually exists. People who are using Ruby seem perfectly happy with dynamic typing. That&#x27;s not &lt;em&gt;particularly&lt;&#x2F;em&gt; surprising, though, and my view might have been influenced by people in my company who are enthusiastic about adding static types to everything? I suppose it might be time to try OODA…&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observe: what am I feeling, why, and what is my impulse?&lt;&#x2F;strong&gt; Uncertainty, because I am wondering if I had a straw man standing in for my audience of the sparse results I&#x27;m getting when searching for Sorbet stuff. My impulse is to say I had a misperception, drop this as a target, and try and find a different audience.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Orient: what are the facts?&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Where am I?&lt;&#x2F;strong&gt; Very early in the process of figuring out what I want to do&#x2F;build.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What&#x27;s happening?&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;There&#x27;s nothing external here that means I need to make a rush decision. Good!&lt;&#x2F;li&gt;
&lt;li&gt;I recently had an opportunity fall through related to Ruby stuff that may be putting me in a more pessimistic mindset about this.&lt;&#x2F;li&gt;
&lt;li&gt;I have also chatting with people who are excited about formal methods&#x2F;modeling and I&#x27;m excited to learn more about that for myself.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What does the data say?&lt;&#x2F;strong&gt; I am mostly basing my opinion on reading chat messages in the Ruby Discord, where there&#x27;s a weird gap: you either have people who say &quot;I use Sorbet for everything and you should too&quot; or people who say &quot;what&#x27;s this Sorbet I keep hearing about?&quot; Nothing in between. No struggles about installation, or expressing types, or anything. This could just mean that people aren&#x27;t using this Discord as a place to get Sorbet-related questions answered, though?&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What do I want?&lt;&#x2F;strong&gt; Same as before—to make a sustainable business, starting by making around $10k on the side in the next year. That&#x27;s the goal, and I&#x27;m not emotionally&#x2F;socially tied to any particular audience (although staying in programming would probably be ideal since I have considerable advantage through experience.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Decide: what will I do?&lt;&#x2F;strong&gt; I might have gone from one kind of bubble (very pro-verification) to another (people who embrace Ruby&#x27;s dynamic nature.) I could look over the other Ruby community spaces and see if the thing I&#x27;m inferring is actually happening across more spaces!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Act!&lt;&#x2F;strong&gt; … the rest of this post&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;OK then…&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Twitter:&lt;&#x2F;strong&gt; Not a lot of movement on Twitter, but I might not be searching hard enough (or it&#x27;s promoting bluechecks or something?)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Ruby on Rails forum:&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discuss.rubyonrails.org&#x2F;search?expanded=true&amp;amp;q=sorbet&quot;&gt;Very few results on the Ruby on Rails forum&lt;&#x2F;a&gt; Several are from folks who are trying to make everything work nicely together and are getting errors. (But one of them says they&#x27;ve &quot;fallen in love&quot; with Sorbet recently, which is encouraging.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Newsletters, dev.to, Ruby linklog, ruby-forum.com:&lt;&#x2F;strong&gt; nothing&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Ruby on Rails Link Slack:&lt;&#x2F;strong&gt; a fair amount of discussion on the Ruby on Rails Link Slack. However, it&#x27;s mostly the extreme &quot;what is this&quot; or &quot;I use this everywhere&quot; stuff. Also someone has a conspiracy theory that the Chan-Zuckerberg Initiative is using Sorbet as a Trojan horse to destroy Ruby? What? People are strange.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&#x2F;r&#x2F;ruby:&lt;&#x2F;strong&gt; only has one discussion thread&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&#x2F;r&#x2F;rails:&lt;&#x2F;strong&gt; very light&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Sorbet Slack:&lt;&#x2F;strong&gt; this is where most of the discussion happens, that I can find&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;StackOverflow:&lt;&#x2F;strong&gt; reasonable amount of questions, but no discussion&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So outside of the Sorbet Slack and SO (which are officially endorsed by the Sorbet team at Stripe&#x2F;Shopify) we&#x27;re talking like under 25 posts where people are sharing pain across my view of the entire Ruby ecosystem. To add to that, the Sorbet Slack is free (so limited to 90 days of posts) and SO doesn&#x27;t really allow discussion in a way that&#x27;s useful to me in finding people&#x27;s deep pains. It seems like the thing I decided to do above (find out if I was looking at a bubble) resolves to &quot;you weren&#x27;t looking at a bubble.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;So, basically, it seems like people are interested in Sorbet, but they&#x27;re not sharing their struggles using it. This is suspicious to me because if Sorbet were being widely adopted I&#x27;d expect people to be sharing way more—it was super hard to set up for me for our Rails app!&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s OODA again:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observe: what am I feeling, why, and what is my impulse?&lt;&#x2F;strong&gt; Reusing the answer from above: uncertain if continuing with Ruby&#x2F;Sorbet is a good idea.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Orient:&lt;&#x2F;strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is happening?&lt;&#x2F;strong&gt; I just found out that my idea about adoption of Sorbet in the Ruby community was a straw man based on my own company&#x27;s behavior around Ruby.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What does the data say?&lt;&#x2F;strong&gt; It says there&#x27;s not a lot I can work with in this community!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What do I want?&lt;&#x2F;strong&gt; Top-level goal is still the same. I want to switch audiences, though.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Decide:&lt;&#x2F;strong&gt; Based on my audience brainstorming before, I think I can widen &quot;Alloy users&quot; to &quot;formal methods practitioners.&quot; You have people looking in from the outside and wanting to get started, you have people willing to pay for services inside the space, you have people wanting to level up their game… or, at least, I think you do!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Act:&lt;&#x2F;strong&gt; for tomorrow and during this week, I&#x27;m going to chat with the people I know in this community and see if I can get a sense of where folks are hanging out online. I&#x27;m fortunate in that I have social connections to a couple people who are active in the formal methods space, so I can just start by asking them!&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 6</title>
		<published>2023-06-27T00:00:00+00:00</published>
		<updated>2023-06-27T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-006/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-006/</id>
		<content type="html">&lt;p&gt;Ten threads. Let&#x27;s go.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;75935405&#x2F;rails-how-to-work-with-serialize-attributes-and-sorbet&quot;&gt;Rails - How to work with serialize attributes and Sorbet&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;pain: how do I get types on a &lt;code&gt;serialize :related_pages, Array&lt;&#x2F;code&gt; call?&lt;&#x2F;li&gt;
&lt;li&gt;comment: don&#x27;t violate first normal form&lt;&#x2F;li&gt;
&lt;li&gt;OP: I just want this to be typed! This table has 10 million rows! My life is hard enough without you telling me to refactor my database!&lt;&#x2F;li&gt;
&lt;li&gt;This question didn&#x27;t have an answer. I answered it because, like, I&#x27;ve been there man.&lt;&#x2F;li&gt;
&lt;li&gt;I forgot how annoying and pedantic SO commenters can be.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;69155000&#x2F;what-is-the-directory-structure-for-adding-sorbet-rbis-to-a-gem&quot;&gt;What is the directory structure for adding Sorbet RBIs to a gem?&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;pain: docs say I need an &lt;code&gt;&#x2F;rbi&lt;&#x2F;code&gt; folder, BUT WHERE?&lt;&#x2F;li&gt;
&lt;li&gt;answer: literally just in the root of the project. A single file is fine. Sorbet is happy.&lt;&#x2F;li&gt;
&lt;li&gt;secondary pain: neither the OP nor the answerer found the docs very clear in this case&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;74842832&#x2F;how-to-configure-sorbet-with-rspec&quot;&gt;How to configure Sorbet with rspec?&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;pain: Sorbet doesn&#x27;t know how to figure out the core rspec DSL, so it&#x27;s complaining that &lt;code&gt;describe&lt;&#x2F;code&gt; isn&#x27;t a valid method.&lt;&#x2F;li&gt;
&lt;li&gt;solution: binding &lt;code&gt;self&lt;&#x2F;code&gt; to &lt;code&gt;T.untyped&lt;&#x2F;code&gt; so Sorbet lets you do whatever. (I&#x27;m not certain that the OP knows that they&#x27;ve just turned off typechecking for all instances of this receiver?)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Alright, ok, you got me. That&#x27;s not ten threads! But it is three, and I took the time to answer one of them. I also looked around and found a handful of threads that I &lt;em&gt;want&lt;&#x2F;em&gt; to look at on the Sorbet Slack, so that&#x27;s some collection done.&lt;&#x2F;p&gt;
&lt;p&gt;As a little status update from yesterday, I asked around in the Ruby discord for help finding more places to look for pain. A few people got confused and thought I was a beginner asking for help, but someone shared a big list with me. Gonna have a look through that soon!&lt;&#x2F;p&gt;
&lt;p&gt;And a thought from yesterday: I went to a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.bellotti.tech&#x2F;courses&quot;&gt;Model Monday&lt;&#x2F;a&gt; yesterday and someone mentioned that professors who teach programmer frequently don&#x27;t actually know the problems that their students are struggling with because the students make all the typical mistakes while doing homework, and the professors only see the finished version. I&#x27;ve taught Sorbet to a bunch of people at work, so I&#x27;ve seen some of the mistakes there, but I wonder if I&#x27;m going to run into the same problems looking online? I don&#x27;t think so, anyway; it seems like if folks are willing to post a problem online they&#x27;re really feeling stuck!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 5</title>
		<published>2023-06-26T00:00:00+00:00</published>
		<updated>2023-06-26T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-005/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-005/</id>
		<content type="html">&lt;p&gt;Lesson 4! This one asks things like &quot;What&#x27;s one thing that people need to know, but don&#x27;t?&quot; and—I&#x27;ll be honest here—I don&#x27;t know them for my chosen audience. I don&#x27;t really interact with the Ruby&#x2F;Sorbet crowd outside my coworkers. In fact, that might be a part of why I think it sounds nicer: I don&#x27;t see any silly drama or encounter many jerks when I&#x27;m working in Ruby-land.&lt;&#x2F;p&gt;
&lt;p&gt;In this lesson, they said that Sales Safari would come later, but I think I need to get into it now. I don&#x27;t have anything better than guessing otherwise! From what I remember from 30x500, the first step is to find places (&quot;watering holes&quot;) that people gather, and then to read through forum threads etc. Then look for the things mentioned in the guidebook in today&#x27;s lesson:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Pain (3 major types)
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;waste&lt;&#x2F;strong&gt; — of time, money, energy, and other resources&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;lack&lt;&#x2F;strong&gt; — of knowledge, skill, awareness, time, money, connections, resources&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;feeling&lt;&#x2F;strong&gt; — of exhaustion, fear, anger, irritation, stress, isolation, inability, uncertainty&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Value (3 major types)
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;saving&lt;&#x2F;strong&gt; — spending less money&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;earning&lt;&#x2F;strong&gt; — earning more money&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;psychological&lt;&#x2F;strong&gt; — pride, acclaim, confidence, respect, satisfaction, relief, etc&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I also remember some specific things to look out for, but that&#x27;s putting the cart before the horse… first I gotta find watering holes!&lt;&#x2F;p&gt;
&lt;p&gt;Ok, so here&#x27;s things I know off the top of my head:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;StackOverflow&lt;&#x2F;li&gt;
&lt;li&gt;the Sorbet users Slack group&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;code&gt;#ruby&lt;&#x2F;code&gt; channel in my local tech watering hole&lt;&#x2F;li&gt;
&lt;li&gt;I guess Ruby forums probably will have some buzz around this and RBS (another gradual typing system)&lt;&#x2F;li&gt;
&lt;li&gt;blog posts&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I confirmed those first two on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sorbet.org&quot;&gt;sorbet.org&lt;&#x2F;a&gt;. Let&#x27;s look at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ruby-lang.org&#x2F;en&#x2F;community&#x2F;&quot;&gt;the Ruby community page&lt;&#x2F;a&gt; next:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ruby-lang.org&#x2F;en&#x2F;community&#x2F;mailing-lists&#x2F;&quot;&gt;mailing lists&lt;&#x2F;a&gt; (maybe! Not gonna get a lot of beginners here in 2023, though.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discord.com&#x2F;servers&#x2F;ruby-518658712081268738&quot;&gt;a Ruby discord&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rubycentral.org&#x2F;&quot;&gt;Ruby Central&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ruby-lang.org&#x2F;en&#x2F;community&#x2F;conferences&#x2F;&quot;&gt;conferences&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ruby-lang.org&#x2F;en&#x2F;community&#x2F;weblogs&#x2F;&quot;&gt;list of blogs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;OK, those don&#x27;t seem &lt;em&gt;super&lt;&#x2F;em&gt; helpful off the bat, but I&#x27;m sure there&#x27;s &lt;em&gt;something&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I also took a look at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;tagged&#x2F;sorbet&quot;&gt;the Sorbet tag on StackOverflow&lt;&#x2F;a&gt; and there&#x27;s, like, a lot of unanswered questions? Not to mention only 151 questions overall. Is that enough to base something nice off of? (doubt doubt doubt!)&lt;&#x2F;p&gt;
&lt;p&gt;One last place I want to look: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rubyweekly.com&#x2F;&quot;&gt;Ruby Weekly&lt;&#x2F;a&gt;. Kind of a legendary newsletter! I bet they have a good read on where people are posting. (But update after 15 minutes looking through archives: they don&#x27;t seem to ever post links to discussions, except very rarely to GitHub issues.)&lt;&#x2F;p&gt;
&lt;p&gt;AHHH yeah, Twitter. Of course Twitter. Programmers are all over there. I didn&#x27;t even consider it because I&#x27;ve been avoiding it since it became the Muskiverse, but I suppose I can still get some value out of it.&lt;&#x2F;p&gt;
&lt;p&gt;Weird that I can&#x27;t find any Discourse forums, since it&#x27;s built on Rails… AH! Ok, there&#x27;s a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discuss.rubyonrails.org&#x2F;&quot;&gt;Rails Discourse with a few Sorbet results&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;All in all, I seem to have two or three really good sources (SO, Slack, Twitter) and a few less-good or maybe more-generic ones. Tomorrow I&#x27;m going to sit down and try to find 10 &quot;threads&quot; (even though these aren&#x27;t forums) that are a) about Sorbet and b) have at least two responses (judging from SO, this might not be easy.)&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 4</title>
		<published>2023-06-22T00:00:00+00:00</published>
		<updated>2023-06-22T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-004/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-004/</id>
		<content type="html">&lt;p&gt;Gooooood morning! Today I&#x27;m gonna try and calculate how many sales, subscribers, visitors, etc I need to hit my financial goal for this project. The roadmap PDF says that, for a mid-range product at $49, you end up needing 28 visits and 5.6 subscribers a day, and 17 sales per month to end up making $10k in a year. I know from launching other small products that sales tend to bunch up around the public launch(es) so it&#x27;s realistically more like 10k visits and 2k subscribers in a short period leading up to launch (assuming the launch is yearly and you don&#x27;t then have, I dunno, end-of-year sales&#x2F;promos or something.)&lt;&#x2F;p&gt;
&lt;p&gt;I messed around in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;soulver.app&#x2F;&quot;&gt;Soulver&lt;&#x2F;a&gt; until my formulas got the same numbers as theirs, then adjusted the numbers for my situation and got the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;goal = $8,500 + 15% (for tax) = $9,775.00&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unit price = $49 = $49.00&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sales needed = goal &#x2F; unit price rounded up = 200&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or monthly, sales needed &#x2F; 12 rounded up = 17&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;selling to subscribers on a mailing list, so…&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sale conversion = 5%&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;subscribe conversion = 10%&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;new subscribers needed = sales needed &#x2F; sale conversion = 4,000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or monthly, new subscribers needed &#x2F; 12 rounded up = 334&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or daily, new subscribers needed &#x2F; 365 = 11&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;new visitors needed = new subscribers needed &#x2F; subscribe conversion = 40,000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or monthly, new visitors needed &#x2F; 12 rounded up = 3,334&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or daily, new visitors needed &#x2F; 365 = 109.59&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My first change was that I am shooting for a slightly different goal because my personal money target is $8,500 (which is very specific, yes, and also a long and annoying story.) In addition, I&#x27;d like to &lt;em&gt;actually&lt;&#x2F;em&gt; make that much instead of that much minus a self-employment tax haircut.&lt;&#x2F;p&gt;
&lt;p&gt;So despite having a slightly smaller goal, the end numbers there (daily subscribers&#x2F;visitors needed) are higher because their formula used a 10% sale conversion (that is, mailing list to buying) and a 20% subscribe conversion (that is, hitting a blog post to subscribing.) I used 5%&#x2F;10% instead because while I think their goals are stretchy but doable, I want to know the numbers I&#x27;d need if my efforts produce something half as good. I&#x27;m still a beginner at this!&lt;&#x2F;p&gt;
&lt;p&gt;This makes my bottom-line number 11 subscribers a day on average off of around 110 visitors per day. Is this realistic for me? I haven&#x27;t blogged anything in a couple of months (oops) but the last thing I posted got to the front page of HN and Lobsters, which resulted in 10,366 views (as of right now.) A somewhat older but evergreen post that also made a splash resulted in 4,066 views in the same period (missing the initial spike.) If I spread those views out over a year, that&#x27;s 28.4&#x2F;day for the big one and 11.1&#x2F; for the evergreen one. Hmm… that&#x27;s pretty far from 110&#x2F;day.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s just two posts, though. Overall, my blog has had 21,198 visits in the period I have data for. That&#x27;s April 4 to June 22 (79 days), which means my blog got 268 visits per day. If I subtract the article that got on HN and the other one it referenced, I&#x27;m left with 188.6 visits per day. Both of those are bigger than the target of 110 by a comfortable margin!&lt;&#x2F;p&gt;
&lt;p&gt;So, OK, I think I&#x27;ve convinced myself that 110 visitors a day is a reasonable target. The roadmap mentions making a freebie available to subscribers, which tends to bring the subscribe conversion up (and therefore the needed visitors down.) I don&#x27;t have data from my older launch to look at sale conversion, but the top of the funnel seems reasonable so I feel OK with proceeding.&lt;&#x2F;p&gt;
&lt;p&gt;Tomorrow I&#x27;ll proceed with lesson four in the guidebook. This diversion took all the time I had for this project today, but I&#x27;m glad I did it!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Update later in the day... I forgot about fees!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;goal = $8,500 + 15% (for tax) = $9,775.00&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unit price = $49 = $49.00&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;net profit = unit price − 10% for fees = $44.10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sales needed = goal &#x2F; net profit rounded up = 222&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or monthly, sales needed &#x2F; 12 rounded up = 19&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;selling to subscribers on a mailing list, so…&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sale conversion = 5%&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;subscribe conversion = 10%&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;new subscribers needed = sales needed &#x2F; sale conversion = 4,440&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or monthly, new subscribers needed &#x2F; 12 rounded up = 370&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or daily, new subscribers needed &#x2F; 365 = 12.2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;new visitors needed = new subscribers needed &#x2F; subscribe conversion = 44,400&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or monthly, new visitors needed &#x2F; 12 rounded up = 3,700&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;or daily, new visitors needed &#x2F; 365 = 121.64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;OK, 122 visits a day needed instead of 110. Doable. And that&#x27;s assuming I use Gumroad, which has ri-di-cu-lous fees of 10%.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 3</title>
		<published>2023-06-20T00:00:00+00:00</published>
		<updated>2023-06-20T00:00:00+00:00</updated>
		<link href="https://bytes.zone/micro/launchftw-003/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-003/</id>
		<content type="html">&lt;p&gt;Lesson 2 is fairly light, but has an important lesson. Big takeaway: learn what people need, want, and are willing to buy. (That was the first rule last time, as well.) I like the framing of &quot;&lt;strong&gt;find misery and fix it.&lt;&#x2F;strong&gt;&quot; This is familiar to me from 30x500, but getting a compressed version like this helps me see the process at a high level!&lt;&#x2F;p&gt;
&lt;p&gt;Well, then, on to lesson 3. Emphasizing that you need to make something people are &lt;em&gt;already&lt;&#x2F;em&gt; willing to buy—&quot;it&#x27;s a million times easier to capture a need than create one.&quot; In other words, skip the traditional startup advice of having an idea and then finding a customer. Instead, find the customer first, then build a custom solution to their problem. To do that, you need to find:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;What does your audience&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;actually do&lt;&#x2F;li&gt;
&lt;li&gt;actually talk about&lt;&#x2F;li&gt;
&lt;li&gt;actually complain about&lt;&#x2F;li&gt;
&lt;li&gt;actually read, share, try, recommend and, of course&lt;&#x2F;li&gt;
&lt;li&gt;actually buy&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The answers are Sales Safari gold.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;So who are your ideal customers? What is your ideal audience? It&#x27;s &quot;the people you are best suited to serve.&quot; In other words, people you already know things about because they&#x27;re people like you.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s an interactive workbook in this step. Before I do it, the tips are to prioritize:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;professional audiences (because they&#x27;re used to paying for things)&lt;&#x2F;li&gt;
&lt;li&gt;audiences where you have some built-in advantage (e.g. because you consult or work in the space.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Copying my answers back here. First question, &quot;List your potential audiences.&quot; As many as you can come up with, one per line. Here are mine:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;formal methods people&lt;&#x2F;li&gt;
&lt;li&gt;Alloy users&lt;&#x2F;li&gt;
&lt;li&gt;TLA+ users&lt;&#x2F;li&gt;
&lt;li&gt;Elm programmers&lt;&#x2F;li&gt;
&lt;li&gt;Rust programmers&lt;&#x2F;li&gt;
&lt;li&gt;Ruby programmers&lt;&#x2F;li&gt;
&lt;li&gt;Sorbet users (subset of Ruby programmers)&lt;&#x2F;li&gt;
&lt;li&gt;makers&lt;&#x2F;li&gt;
&lt;li&gt;3d printer users&lt;&#x2F;li&gt;
&lt;li&gt;laser cutter users&lt;&#x2F;li&gt;
&lt;li&gt;parents&lt;&#x2F;li&gt;
&lt;li&gt;omnifocus users&lt;&#x2F;li&gt;
&lt;li&gt;obsidian note-takers&lt;&#x2F;li&gt;
&lt;li&gt;stationery bike owners&lt;&#x2F;li&gt;
&lt;li&gt;frontend developers&lt;&#x2F;li&gt;
&lt;li&gt;Helix users&lt;&#x2F;li&gt;
&lt;li&gt;Vim users&lt;&#x2F;li&gt;
&lt;li&gt;Kubernetes folks&lt;&#x2F;li&gt;
&lt;li&gt;programmers (generally)&lt;&#x2F;li&gt;
&lt;li&gt;command-line shell users&lt;&#x2F;li&gt;
&lt;li&gt;TDD practitioners&lt;&#x2F;li&gt;
&lt;li&gt;tech leads&lt;&#x2F;li&gt;
&lt;li&gt;staff engineers&lt;&#x2F;li&gt;
&lt;li&gt;Linear users&lt;&#x2F;li&gt;
&lt;li&gt;toolmakers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Second question, narrowing those down to &quot;professional&quot; options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;formal methods people&lt;&#x2F;li&gt;
&lt;li&gt;Alloy users&lt;&#x2F;li&gt;
&lt;li&gt;TLA+ users&lt;&#x2F;li&gt;
&lt;li&gt;Elm programmers&lt;&#x2F;li&gt;
&lt;li&gt;Rust programmers&lt;&#x2F;li&gt;
&lt;li&gt;Ruby programmers&lt;&#x2F;li&gt;
&lt;li&gt;Sorbet users (subset of Ruby programmers)&lt;&#x2F;li&gt;
&lt;li&gt;3d printer users&lt;&#x2F;li&gt;
&lt;li&gt;laser cutter users&lt;&#x2F;li&gt;
&lt;li&gt;omnifocus users&lt;&#x2F;li&gt;
&lt;li&gt;obsidian note-takers&lt;&#x2F;li&gt;
&lt;li&gt;frontend developers&lt;&#x2F;li&gt;
&lt;li&gt;Helix users&lt;&#x2F;li&gt;
&lt;li&gt;Vim users&lt;&#x2F;li&gt;
&lt;li&gt;Kubernetes folks&lt;&#x2F;li&gt;
&lt;li&gt;programmers (generally)&lt;&#x2F;li&gt;
&lt;li&gt;TDD practitioners&lt;&#x2F;li&gt;
&lt;li&gt;tech leads&lt;&#x2F;li&gt;
&lt;li&gt;staff engineers&lt;&#x2F;li&gt;
&lt;li&gt;Linear users&lt;&#x2F;li&gt;
&lt;li&gt;toolmakers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Third question, narrowing those down to ones where I have an advantage. In my case, that mostly means &quot;have I used this in the past and would have some advice for someone just getting started.&quot; I also removed general programming and toolmaking because I have an advantage but it&#x27;s too vague!&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Alloy users&lt;&#x2F;li&gt;
&lt;li&gt;Elm programmers&lt;&#x2F;li&gt;
&lt;li&gt;Rust programmers&lt;&#x2F;li&gt;
&lt;li&gt;Ruby programmers&lt;&#x2F;li&gt;
&lt;li&gt;Sorbet users (subset of Ruby programmers)&lt;&#x2F;li&gt;
&lt;li&gt;frontend developers&lt;&#x2F;li&gt;
&lt;li&gt;TDD practitioners&lt;&#x2F;li&gt;
&lt;li&gt;tech leads&lt;&#x2F;li&gt;
&lt;li&gt;staff engineers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Fourth question is just to pick one to move forward with for launch ftw. This is where I struggle, and the exact question I stopped on the first time I did this challenge (even though they repeatedly say that the only &quot;wrong&quot; answer is to just not pick.)&lt;&#x2F;p&gt;
&lt;p&gt;… and after 15 minutes of flailing about trying to think through it (&quot;well, this, but no, then again, that…&quot;) I&#x27;ve got to try a different approach. I&#x27;ve put the lines into &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elo.bytes.zone&quot;&gt;elo.bytes.zone&lt;&#x2F;a&gt; (a tool that I wrote to help with analysis paralysis) and done head-to-head comparisons until each of them got at least 10 tries. Here&#x27;s the results&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Rank&lt;&#x2F;th&gt;&lt;th&gt;Item&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Rating&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Difference from Last&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;td&gt;Sorbet users&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1307&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;td&gt;Ruby programmers&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1305&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;3&lt;&#x2F;td&gt;&lt;td&gt;frontend developers&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1279&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-26&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;4&lt;&#x2F;td&gt;&lt;td&gt;Alloy users&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1269&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-10&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;5&lt;&#x2F;td&gt;&lt;td&gt;Rust programmers&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1192&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-77&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;6&lt;&#x2F;td&gt;&lt;td&gt;tech leads&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1167&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-25&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;7&lt;&#x2F;td&gt;&lt;td&gt;Elm programmers&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1117&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-50&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;8&lt;&#x2F;td&gt;&lt;td&gt;TDD practitioners&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1076&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-42&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;9&lt;&#x2F;td&gt;&lt;td&gt;staff engineers&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1046&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-30&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;There&#x27;s a big gap between the top four and the rest of the field. For example, I did 30x500 with Elm stuff. It gave me a considerable amount of traction in my career! But I feel burned out in that community; 5 years of running elm-conf took a lot out of me, and I&#x27;m experienced enough in the discourse to be tired of the &quot;permanent problems&quot; and no longer interested in debating them. I see a similar situation developing in Rust, especially with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.jntrnr.com&#x2F;why-i-left-rust&#x2F;&quot;&gt;recent problems in the community&lt;&#x2F;a&gt;. (Although TBH this may be just growing pains due to popularity. I recall Ruby had quite a few community problems when it was super popular.)&lt;&#x2F;p&gt;
&lt;p&gt;So, OK, that leaves basically four things (or more like three, since Sorbet and Ruby are gonna be similar audiences.) Examining each:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Folks who are interested in Sorbet are generally interested in making Ruby code more predictable and safe. Reading between the lines in their marketing and docs, it&#x27;s a similar attraction to folks who have moved from JavaScript to TypeScript.&lt;&#x2F;li&gt;
&lt;li&gt;Rubyists generally are a nice bunch. It&#x27;s a really big circle since it was so popular for so long, which means more eyeballs and dollars in a really general sense.&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Frontend developers&quot; is a little vague… but I really mean people who are interested in developing on the web as a platform. There&#x27;s a ton of stuff happening every year in CSS, HTML, new JS features, new platform features. Hard to keep up! The nature of the web means that this audience should &lt;em&gt;stay&lt;&#x2F;em&gt; pretty large as well.&lt;&#x2F;li&gt;
&lt;li&gt;Alloy users is more niche. It&#x27;d be nice to grow interest in it, but that&#x27;s counter to the advice here about addressing a need rather than creating one (although the need could be &quot;I spent weeks developing this feature, only to find a design flaw that means I have to start over… ARGH!&quot;) Promoting the tool as an alternative seems good, but I&#x27;m not sure where I&#x27;d find people who had that particular pain.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So out of those, folks who are getting into Sorbet seems like the best fit. It&#x27;s a good combination of a large-enough audience and a relevant topic (e.g. there are people starting Ruby projects today as well as folks who are dealing with code written in 2008.)&lt;&#x2F;p&gt;
&lt;p&gt;So, all aboard the Ruby&#x2F;Sorbet train!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 2</title>
		<published>2023-06-15T05:54:00-05:00</published>
		<updated>2023-06-15T05:54:00-05:00</updated>
		<link href="https://bytes.zone/micro/launchftw-002/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-002/</id>
		<content type="html">&lt;p&gt;OK, doing lesson 1 this morning.
&quot;Three rules for success in your launch ftw challenge.&quot;
It starts off with Amy Hoy&#x27;s story of launching a product business.
Key quote for me: &quot;I resolved &#x27;This time, I&#x27;ll do what works&#x27;&quot; after tons of &quot;plan-crastination.&quot;
The daydreams certainly sound like me, although I worry about trying to see myself too much in other peoples&#x27; stories.&lt;&#x2F;p&gt;
&lt;p&gt;So the three most important rules:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;DO learn what people need, want, and will actually buy, and make that&lt;&#x2F;li&gt;
&lt;li&gt;DO design a realistic plan quickly — for a tiny first product, with marketing baked in — so you can hit the ground running and start seeing traffic and sales as soon as possible&lt;&#x2F;li&gt;
&lt;li&gt;DO execute and ship things that are good enough, not perfect, and keep doing it until you build your way up into success&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Summarizing in my own words: find something that people are willing to buy, make ONLY that (no overcomplicating things!) and ship it quickly to get feedback.
Reminds me of the way I try to push at work to ship something small and sharp so that we can get more data—why not apply that mindset to my own products as well?&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>launch ftw, part 1</title>
		<published>2023-06-14T17:18:00-05:00</published>
		<updated>2023-06-14T17:18:00-05:00</updated>
		<link href="https://bytes.zone/micro/launchftw-001/" type="text/html"/>
		<id>https://bytes.zone/micro/launchftw-001/</id>
		<content type="html">&lt;p&gt;I&#x27;m kicking off my microposts (less-edited, ephemeral, subject to disappear at any time) by saying that I&#x27;m about to try out &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackingthebricks.com&#x2F;launchftw&#x2F;&quot;&gt;launch ftw&lt;&#x2F;a&gt; again.
I tried this once before and bounced off of it because I didn&#x27;t find an audience I felt I could commit to.
I&#x27;m trying it again because I watched a video of the instructors, Amy Hoy and Alex Hillman, and they talked a lot about the first step not necessarily being permanent or needing to be the best thing ever, but rather to just try and see where it goes.
That resonated with me, so I&#x27;m going to give it a go!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Modeling Git Internals in Alloy, Part 3: Operations on Blobs and Trees</title>
		<published>2023-04-24T00:00:00+00:00</published>
		<updated>2023-04-24T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-git-internals-in-alloy-part-3-operations-on-blobs-and-trees/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-git-internals-in-alloy-part-3-operations-on-blobs-and-trees/</id>
		<content type="html">&lt;p&gt;In the last two posts, we&#x27;ve modeled Git&#x27;s internal data structures in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;. First, we handled &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-git-internals-in-alloy-part-1-blobs-and-trees&#x2F;&quot;&gt;blobs and trees&lt;&#x2F;a&gt;, then &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-git-internals-in-alloy-part-2-commits-and-tags&#x2F;&quot;&gt;commits and tags&lt;&#x2F;a&gt;. During all that, we introduced some &lt;code&gt;fact&lt;&#x2F;code&gt;s—things that Alloy will accept as true, regardless of whether those assumptions hold in real life. Today, we&#x27;ll model some Git operations to see if those facts actually hold! In particular, we&#x27;ll look at:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hash-object&lt;&#x2F;code&gt; to get a &lt;code&gt;Blob&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;update-index&lt;&#x2F;code&gt; and &lt;code&gt;write-tree&lt;&#x2F;code&gt; to get a &lt;code&gt;Tree&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To do that, we&#x27;re going to have to introduce changes over time to our model. In Alloy, this means two things:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Marking the parts of our model that can change.&lt;&#x2F;li&gt;
&lt;li&gt;Defining transitions between one state and the next.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Once we do that, we can make assertions about time, for example that something is always or never true, or becomes true eventually given some other condition.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s begin by redefining &lt;code&gt;Blob&lt;&#x2F;code&gt; to be able to change over time. To do that, we just prepend &lt;code&gt;var&lt;&#x2F;code&gt; to its definition:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we run this, we get new goodies in the instance viewer:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;blob-over-time-in-the-instance-viewer.png&quot; alt=&quot;The Alloy instance viewer, a macOS window with a row of buttons at the top like &amp;quot;New Init&amp;quot; or &amp;quot;New Fork&amp;quot;. Below the controls is a timeline that shows distinct states. In this instance, there is only one state repeated twice.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On the left, we have time step 0—the initial state of the instance. On the right, we have the next step. Alloy will always show at least two time steps, and in this case Alloy also shows us that the last step repeats forever (by showing a looping arrow over the second step.)&lt;&#x2F;p&gt;
&lt;p&gt;Where we had “New” before to get a new instance, we now click “New Init”—that&#x27;ll get us another starting state for the model. We can also scrub back and forth on the timeline by clicking the left and right arrow buttons. If you click “New Fork”, Alloy will make something new happen starting from the second visible step (the one on the right.) I&#x27;d encourage you to load up Alloy and play around with this some—it&#x27;ll look totally random right now, but that&#x27;s just because we&#x27;re not controlling how &lt;code&gt;Blob&lt;&#x2F;code&gt; changes at all right now, so Alloy allows it to change in arbitrary ways.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;adding-processes&quot;&gt;Adding Processes&lt;&#x2F;h2&gt;
&lt;p&gt;This model isn&#x27;t super useful yet, since we don&#x27;t actually say &lt;em&gt;how&lt;&#x2F;em&gt; blobs can change over time. Let&#x27;s add some structure so we can define that!&lt;&#x2F;p&gt;
&lt;p&gt;To start with, we need to define an initial state. Seems fine to start with the same thing Git does: an empty repo with no objects.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; init&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;d normally use a predicate to check if a condition holds, and that&#x27;s the same here except with the addition of time. Said another way, at any time that &lt;code&gt;init&lt;&#x2F;code&gt; is true, there will be no &lt;code&gt;Blob&lt;&#x2F;code&gt;s. We&#x27;ll place this &lt;code&gt;init&lt;&#x2F;code&gt; at a precise moment in time (namely, the first time step) very soon here.&lt;&#x2F;p&gt;
&lt;p&gt;From there, one reasonable thing to do is to do nothing. We&#x27;ll need a step like this for most models, since Alloy needs to be able to say “… and then nothing ever happens again for the rest of time.” This is traditionally called a stutter step:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; stutter&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We get a new bit of syntax here: saying &lt;code&gt;&#x27;&lt;&#x2F;code&gt; after something means you&#x27;re referring to that thing in the next time step. So &lt;code&gt;Blob&#x27; = Blob&lt;&#x2F;code&gt; (which I pronounce “blob prime equals blob”) means that &lt;code&gt;Blob&lt;&#x2F;code&gt; will stay exactly the same whenever the &lt;code&gt;stutter&lt;&#x2F;code&gt; predicate is true.&lt;&#x2F;p&gt;
&lt;p&gt;Now let&#x27;s combine these! Right now, we can keep the system the same across time by saying “init is true at the beginning, and then stutter is true forever.” We&#x27;d do that with another predicate that traces those events through time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; traces&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always stutter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This introduces another new piece of syntax: &lt;code&gt;always&lt;&#x2F;code&gt; means “from this point going forward, this condition is always true.” In other words, we&#x27;re saying “at the current time step, &lt;code&gt;init&lt;&#x2F;code&gt; is true. Going forward, &lt;code&gt;stutter&lt;&#x2F;code&gt; is always true.” Effectively, that means that we start with no blobs and never add any.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, we tell Alloy that it should assume that the &lt;code&gt;traces&lt;&#x2F;code&gt; predicate is always true:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fact { traces }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now if we evaluate the model, we see that it has exactly zero blobs forever:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;no-blobs-in-the-instance-viewer.png&quot; alt=&quot;The Alloy instance viewer showing a totally uninhabited instance that does not change over time.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If we click “New Init” now, we get the usual “there are no more instances” message:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;no-more-satisfying-instances.png&quot; alt=&quot;a dialog box saying &amp;quot;There are no more satisfying instances. Note: due to symmetry breaking and other optimizations, some equivalent solutions may have been omitted.&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hashing-objects&quot;&gt;Hashing Objects&lt;&#x2F;h2&gt;
&lt;p&gt;Now we have a blank canvas and can start adding operations. Let&#x27;s start as we did in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-git-internals-in-alloy-part-1-blobs-and-trees&#x2F;&quot;&gt;part 1&lt;&#x2F;a&gt; with &lt;code&gt;git hash-object&lt;&#x2F;code&gt;. As a refresher, this command takes some content, hashes it, and stores it according to the hash. You can invoke it like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &amp;#39;Hello, Alloy!&amp;#39; | git hash-object -w --stdin&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;39528abd81b13b2731d47f86206351a61f1e6484&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In Alloy, that looks like adding a new predicate:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; hashObject&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since we only care about the presence of the blobs and not their content, it&#x27;s sufficient to say, “there is one (and only one) blob in the next state that doesn&#x27;t appear in the previous state.”&lt;&#x2F;p&gt;
&lt;p&gt;Next, we&#x27;ll tell &lt;code&gt;traces&lt;&#x2F;code&gt; that &lt;code&gt;hashObject&lt;&#x2F;code&gt; is one of the things that can be true at any time step:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; traces&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    stutter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; hashObject&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Just to reiterate what this means, we&#x27;re now saying “&lt;code&gt;init&lt;&#x2F;code&gt; is true at the start. Forever after, either &lt;code&gt;stutter&lt;&#x2F;code&gt; or &lt;code&gt;hashObject&lt;&#x2F;code&gt; is true.”&lt;&#x2F;p&gt;
&lt;p&gt;If it helps, you can kind of think of this as saying “&lt;code&gt;init&lt;&#x2F;code&gt; is true at the start; afterward, either &lt;code&gt;stutter&lt;&#x2F;code&gt; or &lt;code&gt;hashObject&lt;&#x2F;code&gt; &lt;em&gt;can happen&lt;&#x2F;em&gt;.” However, this is not strictly accurate: Alloy has no concept of events happening, only conditions which happen to be true at any given time.&lt;&#x2F;p&gt;
&lt;p&gt;If you execute this spec, you can now see blobs coming into existence between time steps (you may need to click “New Fork” to get this to happen.)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;one-blob-in-the-instance-viewer.png&quot; alt=&quot;The Alloy instance viewer showing the creation of a single new blob.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;adding-to-the-index&quot;&gt;Adding to the Index&lt;&#x2F;h2&gt;
&lt;p&gt;To finish up, we&#x27;re going to create trees. As a reminder, this is two operations:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Adding a blob to the index at a certain path&lt;&#x2F;li&gt;
&lt;li&gt;Creating the tree from the index&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;On the command line, this looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git update-index --add --cacheinfo 100644 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  39528abd81b13b2731d47f86206351a61f1e6484 hello-alloy.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git write-tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;3ee29075f260c5eebd8b9480b6464a7612668dde&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To model this in Alloy, we&#x27;ll need to model the index as well as trees. First, the index:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  var index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(This is &lt;code&gt;one sig&lt;&#x2F;code&gt; because there can only be one repo in our model. We&#x27;ve assumed there was one implicitly—we wouldn&#x27;t have been able to add blobs otherwise—it&#x27;s just explicit now.)&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ll also need to add trees:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  var children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Like &lt;code&gt;Blob&lt;&#x2F;code&gt;, &lt;code&gt;Tree&lt;&#x2F;code&gt; is &lt;code&gt;var&lt;&#x2F;code&gt;: we can create new trees from an index. But unlike &lt;code&gt;Blob&lt;&#x2F;code&gt;, &lt;code&gt;Tree&lt;&#x2F;code&gt; has children. These are also marked &lt;code&gt;var&lt;&#x2F;code&gt;, despite an individual tree&#x27;s contents never changing, because we need to be able to change the mapping of trees to blobs over time.&lt;&#x2F;p&gt;
&lt;p&gt;Before we can add any events on these, we&#x27;ll need to define what they look like initially, and say that they never change in the stutter step:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; pred init {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   no Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  no Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  no children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; pred stutter {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Blob&amp;#39; = Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  Tree&amp;#39; = Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  children&amp;#39; = children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  index&amp;#39; = index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We could have omitted &lt;code&gt;no children&lt;&#x2F;code&gt; in &lt;code&gt;init&lt;&#x2F;code&gt; because there really can&#x27;t be any trees or blobs present in &lt;code&gt;children&lt;&#x2F;code&gt; if there are no trees or blobs in the whole system, but I find it&#x27;s better to be explicit.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ll also need to add similar lines as a frame condition in &lt;code&gt;hashObject&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; pred hashObject {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   one Blob&amp;#39; - Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  Tree&amp;#39; = Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  children&amp;#39; = children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+  index&amp;#39; = index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We need to do this because of the behavior we observed at first: if we don&#x27;t control the next state of every varying thing in our model, Alloy allows it to change in arbitrary ways. This can be annoying, but so far I&#x27;ve found it&#x27;s something I can get over in order to get the useful stuff Alloy can do.&lt;&#x2F;p&gt;
&lt;p&gt;But now that we have that done, we can add blobs and trees to the index:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; updateIndex&lt;&#x2F;span&gt;&lt;span&gt;[bt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  index&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; index &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Repo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;bt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Our action here is saying “the next version of &lt;code&gt;stage&lt;&#x2F;code&gt; is the old version plus the addition of &lt;code&gt;bt&lt;&#x2F;code&gt;.” We haven&#x27;t seen &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt; before in this post; it means “all of &lt;code&gt;Repo&lt;&#x2F;code&gt; maps to all of &lt;code&gt;bt&lt;&#x2F;code&gt;.” It so happens that we have only one thing in each of those sets, so this is effectively saying “add the mapping of repo-to-bt to the index.”&lt;&#x2F;p&gt;
&lt;aside&gt;
&lt;p&gt;If you want a firmer technical term, &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt; gives you the &lt;em&gt;Cartesian product&lt;&#x2F;em&gt; of the two sides. That means &lt;code&gt;{a, b}-&amp;gt;{1, 2}&lt;&#x2F;code&gt; would be &lt;code&gt;{a-&amp;gt;1, a-&amp;gt;2, b-&amp;gt;1, b-&amp;gt;2}&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;aside&gt;
&lt;p&gt;Now that we have an index, let&#x27;s take care of &lt;code&gt;write-tree&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; writeTree&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Tree {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Repo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  index&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A couple of details here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We&#x27;re dancing around a little by saying “I want one tree in &lt;code&gt;Tree&#x27;&lt;&#x2F;code&gt; that doesn&#x27;t exist in &lt;code&gt;Tree&lt;&#x2F;code&gt;, and then that &lt;code&gt;Tree&#x27;&lt;&#x2F;code&gt; is only changed by that new tree.” I feel this is a little awkward but it&#x27;s the best way I can find to get exactly one new thing. If you&#x27;re an Alloy expert reading this and know a better way to do this, I&#x27;d really appreciate it if you could let me know!&lt;&#x2F;li&gt;
&lt;li&gt;Since &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt; maps &lt;em&gt;all&lt;&#x2F;em&gt; of the left- and right-hand sides, this time we&#x27;re saying “our new tree has all the things currently in the index.”&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;With that taken care of, we need to unstage the items afterward. One final action:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; resetIndex&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; index&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, finally, we can add these predicates to the list of conditions that Alloy will consider at every time step:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; pred traces {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   always {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     stutter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     or hashObject&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+    or (one bt: Blob + Tree | updateIndex[bt])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+    or writeTree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;background-color: #144620;&quot;&gt;+    or resetIndex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point, I&#x27;d usually show some instances from the visualizer and ask if we thought they were reasonable, but that gets harder to do as you add more ways for the system to change. Instead, we&#x27;re going to jump right into…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;making-assertions&quot;&gt;Making Assertions&lt;&#x2F;h2&gt;
&lt;p&gt;Remember how we had this &lt;code&gt;fact&lt;&#x2F;code&gt; back in the static version of the model?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;trees cannot refer to themselves&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We added this because it didn&#x27;t look like it was in the design of Git for trees to refer to themselves (that is, appear in their own children, grandchildren, etc.) Well, now that we&#x27;re modeling operations instead of the data structures alone, we can ask Alloy to think about all the state transitions in the system and tell us whether that&#x27;s allowed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;check TreesCannotReferToThemselves {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always (no t: Tree | t in t.^children)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Remembering that &lt;code&gt;always&lt;&#x2F;code&gt; means “true at this time and forever after”, we&#x27;re asking it to check if there&#x27;s any series of time steps where a tree can appear in its own &lt;code&gt;children&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Alloy can&#x27;t find a counterexample for this—yay! Here&#x27;s what that looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Executing &amp;quot;Check TreesCannotReferToThemselves&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Solver=minisatprover(jni) Steps=1..10 Bitwidth=4 MaxSeq=4 SkolemDepth=1 Symmetry=20 Mode=batch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   1..10 steps. 27665 vars. 1790 primary vars. 67627 clauses. 275ms.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   No counterexample found. Assertion may be valid. 45ms.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   . contains 9 top-level formulas. 10ms.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s also check that the content of trees cannot change with any of our operations. This is as much a unit test of our modeling as it is a property of Git:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; TreesAreImmutable&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; always t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And again, no counterexample found.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, let&#x27;s do something a little trickier. We said it should be OK for files to have the same children—we assume that they&#x27;ll have different filenames. We can ask Alloy to generate this as a counterexample, just to make sure it&#x27;s possible. To do this, we make the opposite assertion (that two distinct trees can&#x27;t have the same children):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; NoDuplicateTrees&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;no&lt;&#x2F;span&gt;&lt;span&gt; disjoint t1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; t2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; t2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We get a counterexample, but not the one we wanted: it turns out that in our model you can call &lt;code&gt;write-tree&lt;&#x2F;code&gt; over and over and get a new tree each time. If you do that in a real repo you&#x27;ll just the same tree over and over.&lt;&#x2F;p&gt;
&lt;p&gt;We could deal with this in a few ways:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We could add a guard condition to &lt;code&gt;writeTree&lt;&#x2F;code&gt; so that it cannot be true if more than one empty tree would be produced. &lt;strong&gt;BUT&lt;&#x2F;strong&gt; in real life, Git allows you to call &lt;code&gt;write-tree&lt;&#x2F;code&gt; at any time.&lt;&#x2F;li&gt;
&lt;li&gt;We could add a precondition to &lt;code&gt;traces&lt;&#x2F;code&gt; to do the same, &lt;strong&gt;BUT&lt;&#x2F;strong&gt; that models someone just never does the wrong thing, so I&#x27;d rather assume that anything can be called at any time.&lt;&#x2F;li&gt;
&lt;li&gt;We could add a &lt;code&gt;fact&lt;&#x2F;code&gt; about empty trees, &lt;strong&gt;but&lt;&#x2F;strong&gt; every fact we have introduces assumptions to our model.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In this case, I think adding a &lt;code&gt;fact&lt;&#x2F;code&gt; is worth it, since we don&#x27;t represent the behavior of empty trees accurately now and introducing a fact helps with accuracy. Here&#x27;s how we do it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;there can only be &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one&lt;&#x2F;span&gt;&lt;span&gt; empty tree&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;lone&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;| no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This hinges on &lt;code&gt;lone&lt;&#x2F;code&gt; enforcing that there will be zero or one &lt;code&gt;Tree&lt;&#x2F;code&gt;s matching this description. We could also say &lt;code&gt;no disjoint t1, t2: Tree | no t1.children and no t2.children&lt;&#x2F;code&gt;, but that&#x27;s a bit of a mouthful.&lt;&#x2F;p&gt;
&lt;p&gt;With the addition of the fact, though, I only see the kinds of traces in the instance viewer that I was expecting. For example:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start off with nothing:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;git-trace-0.png&quot; alt=&quot;An instance with only the repo.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;hash an object:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;git-trace-1.png&quot; alt=&quot;An instance with the repo and a single blob.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;stage the object:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;git-trace-2.png&quot; alt=&quot;The previous instance, but with the blob staged.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;write a tree:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;git-trace-3.png&quot; alt=&quot;The previous instance, but with a tree pointing to the blob. The tree is labeled &amp;quot;no duplicate trees t2&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;without resetting, write another tree:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;git-trace-4.png&quot; alt=&quot;The previous instance, with an additional tree pointing to the blob. The tree is labeled &amp;quot;no duplicate trees t1&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I think we&#x27;re good at this point! Just to recap, today we&#x27;ve modeled:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;blobs and &lt;code&gt;hash-object&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;the plumbing we need to make trees—a repo, the index, staging, and resetting&lt;&#x2F;li&gt;
&lt;li&gt;trees and &lt;code&gt;write-tree&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Next time we&#x27;ll go a bit further by adding commits. Stay tuned!&lt;&#x2F;p&gt;
&lt;p&gt;Before I leave you, though, here&#x27;s the complete model from this post. It&#x27;s quite a bit longer than other ones we&#x27;ve worked with, but it&#x27;s also doing way more:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; hashObject&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  index&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  var children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;there can only be &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one&lt;&#x2F;span&gt;&lt;span&gt; empty tree&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;lone&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;| no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  var index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; updateIndex&lt;&#x2F;span&gt;&lt;span&gt;[bt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  index&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; index &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Repo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;bt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; writeTree&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Tree {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Repo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  index&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; resetIndex&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; index&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; frame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; init&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; stutter&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Blob&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Tree&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  index&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; traces&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    stutter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; hashObject&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one&lt;&#x2F;span&gt;&lt;span&gt; bt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; updateIndex&lt;&#x2F;span&gt;&lt;span&gt;[bt])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; writeTree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    or&lt;&#x2F;span&gt;&lt;span&gt; resetIndex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; { traces }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; TreesCannotReferToThemselves&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;children)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; TreesAreImmutable&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; always t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; NoDuplicateTrees&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  always (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;no&lt;&#x2F;span&gt;&lt;span&gt; disjoint t1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; t2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; t2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Modeling Git Internals in Alloy, Part 2: Commits and Tags</title>
		<published>2023-04-10T00:00:00+00:00</published>
		<updated>2023-04-10T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-git-internals-in-alloy-part-2-commits-and-tags/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-git-internals-in-alloy-part-2-commits-and-tags/</id>
		<content type="html">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-git-internals-in-alloy-part-1-blobs-and-trees&#x2F;&quot;&gt;Last week&lt;&#x2F;a&gt;, we started modeling Git&#x27;s internals in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;. We added blobs (to store content) and trees (to organize it into a filesystem.) We ended up with this model:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;abstract sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;trees cannot refer to themselves&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;… which produces instances that look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;normal-trees-and-blobs.png&quot; alt=&quot;an Alloy instance showing a tree containing another tree and a blob. The child tree contains a second blob.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Today, we&#x27;re going to add commits and tags to this model!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;commits&quot;&gt;Commits&lt;&#x2F;h2&gt;
&lt;p&gt;Going back to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;book.git-scm.com&#x2F;book&#x2F;en&#x2F;v2&#x2F;Git-Internals-Git-Objects&quot;&gt;the &lt;em&gt;Git Internals - Git Objects&lt;&#x2F;em&gt; chapter of the Git book&lt;&#x2F;a&gt;, we can take a tree hash we produced in the last post and make a commit with &lt;code&gt;git commit-tree&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit-tree 3ee29075 -m &amp;#39;Commit message&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8cc0d4f4ddfde6efa9a8fced667d4d51574a36ec&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can add more commits (and history) by repeating this command, but specifying a parent ID for each subsequent commit.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit-tree 3ee29075 -m &amp;#39;Second commit&amp;#39; -p 8cc0d4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bc8d9d27a206d0e933be3e445c82cbef09da54d1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit-tree 3ee29075 -m &amp;#39;Third commit&amp;#39; -p bc8d9d&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;844bcca25118c27b0322aacd49edb73d8fac827f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then we can view the lineage of the most recent commit with &lt;code&gt;git log&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git log 844bcc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;commit 844bcca25118c27b0322aacd49edb73d8fac827f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Author: Brian Hicks &amp;lt;brian@brianthicks.com&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Date:   Fri Mar 3 12:36:14 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Third commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;commit bc8d9d27a206d0e933be3e445c82cbef09da54d1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Author: Brian Hicks &amp;lt;brian@brianthicks.com&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Date:   Fri Mar 3 12:35:49 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Second commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;commit 8cc0d4f4ddfde6efa9a8fced667d4d51574a36ec&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Author: Brian Hicks &amp;lt;brian@brianthicks.com&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Date:   Fri Mar 3 12:32:37 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Commit message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But… would it work to commit a blob hash, or does it only work with tree? The book doesn&#x27;t say, so I tried, and it looks like the hash you pass in as the first argument to git commit-tree must be a tree. If you try to make a commit based on a blob, git won&#x27;t let you:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git cat-file -p 3ee29075&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100644 blob 39528abd81b13b2731d47f86206351a61f1e6484    hello-alloy.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100644 blob 9b4b40c2bca67e781930105fa190b9b90235cfe5    hello-blob.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git cat-file -p 39528a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hello, Alloy!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit-tree 39528a -m &amp;#39;Can you commit a blob?&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fatal: 39528abd81b13b2731d47f86206351a61f1e6484 is not a valid &amp;#39;tree&amp;#39; object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So it looks like a commit has to have a tree, a message, and zero or more parents (you can have more than one; this is how merge commits work.) All this is confirmed by &lt;code&gt;man git-commit-tree&lt;&#x2F;code&gt;! We&#x27;ll leave messages out of our model because they don&#x27;t matter for any properties we might care about, but otherwise we&#x27;ll add this to our model:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  parent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;finding-mismatches-between-git-s-model-and-ours&quot;&gt;Finding mismatches between Git&#x27;s model and ours&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s look at the instances Alloy produces and see if we think any of that feels off. To start, we get relatively normal-looking instances, such as two commits with the same tree:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;two-commits-pointing-to-the-same-tree.png&quot; alt=&quot;An Alloy instance showing two commits referencing the same tree.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;But we also get some wilder instances. For example, it looks like our model allows trees to have commits as children:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;tree-with-commit-child.png&quot; alt=&quot;An Alloy instance showing a tree with a commit as a child.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not sure whether that&#x27;d be allowed, but it&#x27;s easy to verify by asking Git to add a commit to the staging area:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git update-index --add --cacheinfo 100644 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  8cc0d4 commits-are-stageable.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fatal: git update-index: --cacheinfo cannot add 8cc0d4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nope, doesn&#x27;t work. That&#x27;s fine. We&#x27;ll just update our definition of &lt;code&gt;Tree&lt;&#x2F;code&gt; to say that they can&#x27;t have commits as children. Since we&#x27;re dealing with sets here, we can write “all objects besides commits” as &lt;code&gt;Object - Commit&lt;&#x2F;code&gt;, which makes the new definition of &lt;code&gt;Tree&lt;&#x2F;code&gt; look like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Object &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s not all the weirdness taken care of, though: we also get commits which are their own parents, or cycles of commits who are each other&#x27;s parents:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;commits-who-are-their-own-parents.png&quot; alt=&quot;An Alloy instance showing a commit which has itself as a parent.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Like last time, this is &lt;em&gt;technically&lt;&#x2F;em&gt; possible: if you can find just the right content for the commit messages and trees, you could conceivably get a commit to refer to itself. Like before, though, this is likely to break git in some awful ways (segfaults!) If we were modeling Git to try to find bugs or security vulnerabilities, I&#x27;d say we should allow this. But, as before, we&#x27;re trying to learn how this is &lt;em&gt;supposed&lt;&#x2F;em&gt; to work, so let&#x27;s disallow it in the same way we disallowed trees being their own parent:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;commits can&amp;#39;t be their own parent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Commit &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;parent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;tags&quot;&gt;Tags&lt;&#x2F;h2&gt;
&lt;p&gt;With commits done, we have only one more object type to model: the tag. Tags are like commits, but instead of pointing to a tree and parent they point to a commit, and you can move them later (as opposed to everything else we&#x27;ve seen so far, which is immutable.) Here&#x27;s how we&#x27;d model that:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running the model like this shows that we&#x27;ve implicitly allowed trees to contain tags (because now &lt;code&gt;Object - Commit&lt;&#x2F;code&gt; includes &lt;code&gt;Tag&lt;&#x2F;code&gt;) which we didn&#x27;t mean. We &lt;em&gt;could&lt;&#x2F;em&gt; say &lt;code&gt;Object - Commit - Tag&lt;&#x2F;code&gt;, but at this point I think it&#x27;d be better to rephrase &lt;code&gt;Tree.children&lt;&#x2F;code&gt; to contain only what we want:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can get tags on commits. Yay!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;tagged-commit.png&quot; alt=&quot;An Alloy instance showing a tag attached to a commit.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ve now reached the end of the first part of our Git-modeling journey: we have all the objects! (There are also refs, though, which work like tags but aren&#x27;t stored with the git objects. You can read more about those in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;book.git-scm.com&#x2F;book&#x2F;en&#x2F;v2&#x2F;Git-Internals-Git-References&quot;&gt;the Git Internals - Git References chapter of the Git book&lt;&#x2F;a&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the model we&#x27;re finishing with:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;abstract sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;trees cannot refer to themselves&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  parent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;commits can&amp;#39;t be their own parent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Commit &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;parent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From here, our next step is to model the operations we can take on this model to check if the properties we wrote earlier actually hold when we use Git&#x27;s commands. Stay tuned!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Modeling Git Internals in Alloy, Part 1: Blobs and Trees</title>
		<published>2023-04-03T00:00:00+00:00</published>
		<updated>2023-04-03T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-git-internals-in-alloy-part-1-blobs-and-trees/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-git-internals-in-alloy-part-1-blobs-and-trees/</id>
		<content type="html">&lt;p&gt;Today we&#x27;re going to learn some &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; by modeling &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;&quot;&gt;Git&lt;&#x2F;a&gt; objects.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-alloy&quot;&gt;What&#x27;s Alloy?&lt;&#x2F;h2&gt;
&lt;p&gt;I wrote this post for people who are meeting Alloy for the first time. If that&#x27;s you, here&#x27;s a quick intro: Alloy is a piece of software (with its own domain-specific programming language) used for making and checking models of other software. You use it to do &quot;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Formal_methods#Lightweight_formal_methods&quot;&gt;lightweight formal methods&lt;&#x2F;a&gt;&quot;, focusing on approachable language and practical applications over full specification and formal proofs.&lt;&#x2F;p&gt;
&lt;p&gt;Alloy helps you think through problems by showing you both what&#x27;s possible and implied in the models you give it. It can model whatever you care to express in its language, although some things lend themselves to modeling better than others. Some things I&#x27;ve had good luck with: data structures, database schemas, and UI states.&lt;&#x2F;p&gt;
&lt;p&gt;Alloy can exhaustively check conditions in models &lt;em&gt;under a certain size&lt;&#x2F;em&gt;. This gives us a tradeoff: you don&#x27;t get a full proof that your condition holds (although you can get close enough for most purposes) but it can execute your specs much faster than other tools in the formal methods space. This is almost always fine. Just be cautious about saying things like &quot;I &lt;em&gt;proved&lt;&#x2F;em&gt; this using Alloy&quot;—it&#x27;s not that kind of tool.&lt;&#x2F;p&gt;
&lt;p&gt;You can download Alloy at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.org&quot;&gt;alloytools.org&lt;&#x2F;a&gt;. If you&#x27;re interested in learning more after reading this, I&#x27;ve also written &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-database-tables-in-alloy&#x2F;&quot;&gt;modeling database tables in Alloy&lt;&#x2F;a&gt; as well as a &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;few&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;the-value-of-a-model-is-more-making-than-having&#x2F;&quot;&gt;other&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;fields-as-sets&#x2F;&quot;&gt;posts&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;git-s-internals&quot;&gt;Git&#x27;s Internals&lt;&#x2F;h2&gt;
&lt;p&gt;Now, to Git. Git stores all the code and commits in your repo in a content-addressable store. That means that if you know the hash of something, you can retrieve it from the store. This allows Git to do cool things like syncing and deduplication, but it&#x27;s also the source of some of the weirder parts of Git&#x27;s behavior from a beginner&#x27;s perspective. Once I learned about the internals, I found it a lot easier to reason about what it was doing. If you&#x27;re learning about them for the first time now, I hope you have a similar experience!&lt;&#x2F;p&gt;
&lt;p&gt;I based these models off of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;book.git-scm.com&#x2F;book&#x2F;en&#x2F;v2&#x2F;Git-Internals-Git-Objects&quot;&gt;the &lt;em&gt;Git Internals - Git Objects&lt;&#x2F;em&gt; chapter of the Git book&lt;&#x2F;a&gt;. I won&#x27;t go into nearly as much detail about how Git works as they do there; this post will be more about modeling. If learning both the modeling tool and the domain at once is too much, go read that chapter and come back.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;blobs&quot;&gt;Blobs&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s look at blobs (used for basic content storage) first. We can tell &lt;code&gt;git&lt;&#x2F;code&gt; to create one like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &amp;#39;Hello, blob!&amp;#39; | git hash-object -w --stdin&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;9b4b40c2bca67e781930105fa190b9b90235cfe5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Git gives us a hash of the content on stdout. To retrieve it again, we just ask:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git cat-file -p 9b4b40c2bca67e781930105fa190b9b90235cfe5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hello, blob!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Like I mentioned above, these blob objects are content-addressed. In practice, that means they can&#x27;t be duplicated. If you run the command to store &lt;code&gt;Hello, blob!&lt;&#x2F;code&gt; twice, Git will give you the hash again but not change anything on disk (since the blob is already stored.) We can get a &lt;em&gt;new&lt;&#x2F;em&gt; blob, along with a new hash, by changing the content:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &amp;#39;Hello, Alloy!&amp;#39; | git hash-object -w --stdin&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;39528abd81b13b2731d47f86206351a61f1e6484&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(Git produces these hashes consistently across different installations, by the way. Try running them in some repo on your computer. You should get the same hashes!)&lt;&#x2F;p&gt;
&lt;p&gt;Since this is basically the same behavior as sets, we can model the whole thing in Alloy like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This introduces a &lt;code&gt;sig&lt;&#x2F;code&gt;, our basic unit of modeling. I find it easiest to think of &lt;code&gt;sig&lt;&#x2F;code&gt;s as sets: we can have zero &lt;code&gt;Blob&lt;&#x2F;code&gt;s, or one, or many, but never more than one of the same &lt;code&gt;Blob&lt;&#x2F;code&gt; in the set.&lt;&#x2F;p&gt;
&lt;p&gt;We can ask Alloy to execute this model, giving us examples of &lt;code&gt;Blob&lt;&#x2F;code&gt;. If we do so, then click &quot;Show&quot;, it gives us a window like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;alloy-show-one-blob.png&quot; alt=&quot;the Alloy evaluator showing a single blob instance, a box labeled blob&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can click &quot;New&quot; a bunch of times here. Alloy will start off with no blobs, then show one, then two, three, and four. At four, if you click &quot;New&quot; again Alloy will tell you that &quot;There are no more satisfying instances.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;no-more-satisfying-instances.png&quot; alt=&quot;a dialog box saying &amp;quot;There are no more satisfying instances. Note: due to symmetry breaking and other optimizations, some equivalent solutions may have been omitted.&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This means that we&#x27;ve seen all the possible ways for there to be blobs under the current configuration. Alloy limits us to four of each &lt;code&gt;sig&lt;&#x2F;code&gt; by default. We can raise this if we need, but in my experience Alloy can find most interesting things under this limit.&lt;&#x2F;p&gt;
&lt;p&gt;At any rate, no matter how many blobs we have, we still haven&#x27;t modeled enough to show useful things about Git. Next up in the Git book: trees!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trees&quot;&gt;Trees&lt;&#x2F;h2&gt;
&lt;p&gt;Trees work basically like directories in a filesystem hierarchy. Their hash is based on the hash of all the content they contain, plus the filenames of that content. We can create one by associating blobs with filenames in the index (kinda like &lt;code&gt;git add foo.txt&lt;&#x2F;code&gt; but with the object hash instead of reading from the project source.) It looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git update-index --add --cacheinfo 100644 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  9b4b40c2bca67e781930105fa190b9b90235cfe5 hello-blob.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git update-index --add --cacheinfo 100644 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  39528abd81b13b2731d47f86206351a61f1e6484 hello-alloy.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git write-tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;3ee29075f260c5eebd8b9480b6464a7612668dde&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives us a tree object containing the hashes we wrote before. We &lt;em&gt;might&lt;&#x2F;em&gt; try and model trees like this in Alloy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But that won&#x27;t work: trees can also contain trees! We can demonstrate using &lt;code&gt;git read-tree&lt;&#x2F;code&gt; (it works like &lt;code&gt;update-index --add&lt;&#x2F;code&gt;, but puts the content of the tree under the given prefix.)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git reset --hard # to clear out the staged files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git read-tree --prefix=greetings 3ee29075f260c5eebd8b9480b6464a7612668dde&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git write-tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;a0776403e71047444987c67405757a1dbeb0f263&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git cat-file -p a0776403e71047444987c67405757a1dbeb0f263&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;040000 tree 3ee29075f260c5eebd8b9480b6464a7612668dde    greetings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We need to change our modeling to allow for this. Luckily for us, it turns out that both blobs and trees belong to the category of &quot;Git objects.&quot; Alloy makes this easy to model:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;abstract sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A &lt;code&gt;sig&lt;&#x2F;code&gt; being &lt;code&gt;abstract&lt;&#x2F;code&gt; means that there aren&#x27;t ever going to be any things that are &lt;em&gt;only&lt;&#x2F;em&gt; &lt;code&gt;Object&lt;&#x2F;code&gt;, but that &lt;code&gt;Object&lt;&#x2F;code&gt; can serve as a superset for anything extending it (you can think of it like an abstract class in an object-oriented language.)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Blob&lt;&#x2F;code&gt; and &lt;code&gt;Tree&lt;&#x2F;code&gt; are now defined as &lt;em&gt;extending&lt;&#x2F;em&gt; &lt;code&gt;Object&lt;&#x2F;code&gt;. This means that they are non-overlapping subsets of &lt;code&gt;Object&lt;&#x2F;code&gt;. In other words, nothing is both a &lt;code&gt;Blob&lt;&#x2F;code&gt; and a &lt;code&gt;Tree&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, we give &lt;code&gt;Tree&lt;&#x2F;code&gt; some &lt;code&gt;children&lt;&#x2F;code&gt;. Defining it with &lt;code&gt;set&lt;&#x2F;code&gt; means that we could have zero, one, or many children (which is possible in &lt;code&gt;git&lt;&#x2F;code&gt;; I checked on an empty repo.) If we want one or more, we could say &lt;code&gt;some&lt;&#x2F;code&gt;. There&#x27;s also &lt;code&gt;one&lt;&#x2F;code&gt; (which is what it sounds like) and &lt;code&gt;lone&lt;&#x2F;code&gt; (which is zero or one exactly.) These are all called &quot;multiplicity operators.&quot; You don&#x27;t have to specify one, although I always do because I forget what the default is and it&#x27;s nicer to be explicit anyway.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s execute this model and have a look at some of the instances. For example, we can get totally normal-looking trees which contain other trees and blobs:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;normal-trees-and-blobs.png&quot; alt=&quot;an Alloy instance showing a tree containing another tree and a blob. The child tree contains a second blob.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;However, we&#x27;ve accidentally allowed some other stuff like…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;recursion-dun-dun-dunnnnn&quot;&gt;Recursion! (dun dun dunnnnn)&lt;&#x2F;h2&gt;
&lt;p&gt;Our current model allows the data structures we want, but it also allows stuff that doesn&#x27;t make sense in Git. For example, trees can contain themselves, and contain the trees that contain them. Here&#x27;s an example with all that happening at once:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;trees-containing-themselves-and-each-other.png&quot; alt=&quot;an Alloy instance showing two trees which both have each other and themselves as children.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This shouldn&#x27;t be possible, theoretically. Remember that this is a content-addressed storage system, so you need to know something&#x27;s hash to refer to it. That means for something to refer to itself, you&#x27;d have to know the hash in advance, and the hash of the thing you create would change depending on that hash. Since hash algorithms aren&#x27;t perfect, you can get two values that produce the same hash (a collision) but you&#x27;d have to get really lucky (or unlucky, depending on your perspective.)&lt;&#x2F;p&gt;
&lt;p&gt;I was curious about whether this was actually even possible or if Git would reject it, so I did some digging. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;git&#x2F;comments&#x2F;6kkb3k&#x2F;a_tree_that_references_itself&#x2F;&quot;&gt;Someone on Reddit&lt;&#x2F;a&gt; had the same question and changed the source code to be dramatically more likely to have a collision (by changing the code to only use the first byte of the SHA-1 hash.) They got a segfault, which tells me that this isn&#x27;t something that the Git authors designed for.&lt;&#x2F;p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;strong&gt;Sidebar: what are we modeling, again?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We hit a fork in the road here in practical modeling terms: we can either allow or disallow this case in our model. This happens to me a lot when using Alloy; it&#x27;s good at bringing up weird cases I hadn&#x27;t previously considered. In this case, I think you can make a good argument for either direction:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If we allow recursion in trees, we&#x27;re keeping our eyes open to the fact that SHA-1 is not completely collision-free, or that some day it may be broken in a way that makes it trivial to find collisions.&lt;&#x2F;li&gt;
&lt;li&gt;If we disallow recursion in trees, we simplify our conceptual model significantly. We&#x27;re not nearly done with the data model (we still have commits, refs, tags, etc to add) and allowing every single little edge case will make it harder to understand the system as a whole.
In this case we&#x27;re modeling to learn about Git&#x27;s internals, so I think disallowing this case makes more sense. Git clearly relies on the fact that it&#x27;s very hard to cause a cycle here. If I was modeling Git objects to (for example) find bugs or potential security issues, I think I&#x27;d make the opposite decision.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;aside&gt;
&lt;p&gt;We&#x27;ll use a &lt;code&gt;fact&lt;&#x2F;code&gt; to disallow this case. When we declare something as &lt;code&gt;fact&lt;&#x2F;code&gt;, Alloy will not allow any instances which would make the fact false. We should use these carefully—as in other kinds of reasoning, more axioms mean making a weaker argument (even though it&#x27;s hard to not use any at all.)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;trees cannot refer to themselves&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;^&lt;&#x2F;code&gt; operator is new to us—it follows the &quot;children&quot; relationship one or more times. That means that we&#x27;re saying &quot;there is no &lt;code&gt;Tree&lt;&#x2F;code&gt; that is it&#x27;s own child, or that child&#x27;s child, or that child&#x27;s child&#x27;s child, and so on.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;Once we do this, Alloy cannot find any more instances where trees refer to themselves, but it can still find plenty where trees contain other trees or blobs. Success!&lt;&#x2F;p&gt;
&lt;p&gt;We &lt;em&gt;can&lt;&#x2F;em&gt;, however, still get two trees pointing to the same blob. If we assume that these two trees are using the same filename for the blob, this shouldn&#x27;t be possible, because they&#x27;d have the same hash.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;two-trees-pointing-to-the-same-blob.png&quot; alt=&quot;an Alloy instance showing two trees containing the same child blob.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So we find ourselves at another fork in the road: we could either model naming files within trees or not. Like last time, you can make a convincing argument either way:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If we add names, we&#x27;ll be sticking closer to the way the actual system works. Trees exist as the equivalent of a directory in a filesystem, so it&#x27;s a little weird to ignore names totally.&lt;&#x2F;li&gt;
&lt;li&gt;If we don&#x27;t add names, we&#x27;ll keep the model simpler. However, we can assume that any time we see two trees with the same contents, at least child has a different name. Since a directory entry is a string, and there are effectively an infinite amount of strings, this seems like a safe assumption.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Again, I have to ask &lt;em&gt;what we&#x27;re modeling&lt;&#x2F;em&gt;. If, right now, we were modeling the way that &lt;code&gt;git checkout&lt;&#x2F;code&gt; turns a tree into files and directories in the filesystem, we might want to add names. But since we&#x27;re trying to understand Git&#x27;s internal structure, it probably makes sense to leave them out (it makes our &lt;code&gt;fact&lt;&#x2F;code&gt; above much nicer, for one.) I usually add a comment documenting this, though, and I will in our final model.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;that-s-all-for-now&quot;&gt;That&#x27;s all… for now&lt;&#x2F;h2&gt;
&lt;p&gt;Today we&#x27;ve learned: how to make a &lt;code&gt;sig&lt;&#x2F;code&gt;, how to connect two &lt;code&gt;sig&lt;&#x2F;code&gt;s together, and how to force Alloy to disallow certain cases. We&#x27;ve also (maybe) learned a little bit about blobs and trees. The full, final model is below:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;abstract sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Object&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; We&amp;#39;re not modeling the mapping from name to object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; for simplicity&amp;#39;s sake. If you see two trees with the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; same children, assume it&amp;#39;s fine because the children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; have different file names.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;trees cannot refer to themselves&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tree &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Over the next couple of posts, we&#x27;ll build on this by:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;adding more Git object! Commits, references, and tags.&lt;&#x2F;li&gt;
&lt;li&gt;modeling how commands like &lt;code&gt;git hash-object -w&lt;&#x2F;code&gt;, &lt;code&gt;git update-index&lt;&#x2F;code&gt;, and &lt;code&gt;git write-tree&lt;&#x2F;code&gt; modify the object database to verify that statements like &quot;trees can&#x27;t refer to themselves&quot; are actually true given the operations we can run.&lt;&#x2F;li&gt;
&lt;li&gt;modeling how everyday Git commands (the &quot;porcelain&quot;; things like &lt;code&gt;git add&lt;&#x2F;code&gt; and &lt;code&gt;git commit&lt;&#x2F;code&gt;) map onto these lower-level operations.&lt;&#x2F;li&gt;
&lt;li&gt;and, if we&#x27;re very lucky, we can model how &lt;code&gt;git&lt;&#x2F;code&gt; syncs this all around when you run &lt;code&gt;git push&lt;&#x2F;code&gt; or &lt;code&gt;git pull&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Stay tuned! If you&#x27;d like to be notified when I publish any of those posts, you can subscribe below.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>aligning Markdown tables in Helix</title>
		<published>2023-03-13T00:00:00+00:00</published>
		<updated>2023-03-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/aligning-markdown-tables-in-helix/" type="text/html"/>
		<id>https://bytes.zone/posts/aligning-markdown-tables-in-helix/</id>
		<content type="html">&lt;p&gt;One of the things I like most about using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;helix-editor.com&#x2F;&quot;&gt;Helix&lt;&#x2F;a&gt; (and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kakoune.org&#x2F;&quot;&gt;Kakoune&lt;&#x2F;a&gt;, when I used it before) is the multiple cursor support. I use them whenever I can! Today, I want to share how Helix solves a common task: formatting a Markdown table.&lt;&#x2F;p&gt;
&lt;p&gt;When I first write Markdown tables, they tend to look like this (with some data from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.kaggle.com&#x2F;datasets&#x2F;rtatman&#x2F;lego-database?select=parts.csv&quot;&gt;this LEGO Database on Kaggle&lt;&#x2F;a&gt;.) Ragged-right formatting and just enough syntax for a parser to tell the headers from the rows:&lt;&#x2F;p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| ID | Name | Category ID |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|-|-|-|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11092 | Gorilla Fist | 27 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11212 | Plate 3 x 3 | 14 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11209 | Tyre 21 x 9.9 | 29 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11640pr0003 | ELECTRIC GUITAR SHAFT Ø3.2 NO. 3 | 27 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This looks fine when rendered, but it&#x27;s somewhat annoying in the source. In other editors I&#x27;ve used, you either need to do the alignment by hand (tedious!) or use some plugin. In Helix and Kakoune, doing this is fairly simple: you just need to combine some editing primitives.&lt;&#x2F;p&gt;
&lt;p&gt;Bottom-line-up-front, the key sequence is &lt;code&gt;mips\|&amp;lt;ret&amp;gt;&amp;amp;&lt;&#x2F;code&gt;. But that looks just like line noise, so let&#x27;s break it down:&lt;&#x2F;p&gt;
&lt;p&gt;First, I hit &lt;code&gt;mip&lt;&#x2F;code&gt; to select the whole paragraph, then select pipes (&lt;code&gt;s\|&amp;lt;ret&amp;gt;&lt;&#x2F;code&gt;.) That gives me one selection per pipe character:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;one-selection-per-character-in-a-markdown-table.png&quot; alt=&quot;screenshot of Helix, in which each pipe character from the code sample above is selected by a rectangular cursor.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then I hit &lt;code&gt;&amp;amp;&lt;&#x2F;code&gt;, the “align selections” operator, which gets me the final alignment:&lt;&#x2F;p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| ID          | Name                             | Category ID |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|-            |-                                 |-            |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11092       | Gorilla Fist                     | 27          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11212       | Plate 3 x 3                      | 14          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11209       | Tyre 21 x 9.9                    | 29          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11640pr0003 | ELECTRIC GUITAR SHAFT Ø3.2 NO. 3 | 27          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This still renders just fine, but it&#x27;s a little nicer reading experience to have the line separating the headers from the body rows full of hyphens. That one&#x27;s easy too: after dismissing the selections with &lt;code&gt;,&lt;&#x2F;code&gt;, you can navigate to the row, hit &lt;code&gt;x&lt;&#x2F;code&gt; to select the whole line, then &lt;code&gt;s&lt;&#x2F;code&gt; to select characters, enter space as the section, and &lt;code&gt;r-&lt;&#x2F;code&gt; to replace all the selections with hyphens. Now it&#x27;s a perfectly nice Markdown table that&#x27;s very pleasant to read in the source:&lt;&#x2F;p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| ID          | Name                             | Category ID |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|-------------|----------------------------------|-------------|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11092       | Gorilla Fist                     | 27          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11212       | Plate 3 x 3                      | 14          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11209       | Tyre 21 x 9.9                    | 29          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 11640pr0003 | ELECTRIC GUITAR SHAFT Ø3.2 NO. 3 | 27          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>how to add weak fairness on actions</title>
		<published>2023-03-06T00:00:00+00:00</published>
		<updated>2023-03-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/how-to-add-weak-fairness-on-actions/" type="text/html"/>
		<id>https://bytes.zone/posts/how-to-add-weak-fairness-on-actions/</id>
		<content type="html">&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;learning-alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;, you can add weak fairness to actions (that is, ensure that they&#x27;ll always eventually fire if they&#x27;re able) by saying:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(eventually always p) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; (always eventually q)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In English, this means: &quot;if &lt;code&gt;p&lt;&#x2F;code&gt; is eventually true, then &lt;code&gt;q&lt;&#x2F;code&gt; will eventually be true at all points afterwards.&quot; In other words, &lt;code&gt;p&lt;&#x2F;code&gt; is the condition under which &lt;code&gt;q&lt;&#x2F;code&gt; can happen.&lt;&#x2F;p&gt;
&lt;p&gt;Note that you should be careful about existential (&lt;code&gt;some&lt;&#x2F;code&gt;) vs universal (&lt;code&gt;all&lt;&#x2F;code&gt;) quantifiers with this kind of statement. For example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Thing {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (eventually always &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;enabled&lt;&#x2F;span&gt;&lt;span&gt;[t]) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;implies&lt;&#x2F;span&gt;&lt;span&gt; (always eventually &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;do&lt;&#x2F;span&gt;&lt;span&gt;[t])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Saying &lt;code&gt;all&lt;&#x2F;code&gt; here means that every time the condition &lt;code&gt;enabled&lt;&#x2F;code&gt; is true the action &lt;code&gt;do&lt;&#x2F;code&gt; is eventually taken. If this said &lt;code&gt;some t: Thing&lt;&#x2F;code&gt; instead, that would mean that this condition would only be true some of the time.&lt;&#x2F;p&gt;
&lt;p&gt;Source: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.discourse.group&#x2F;t&#x2F;how-do-you-say-once-this-is-true-its-always-true&#x2F;294&quot;&gt;Alcino Cunha&#x27;s answer to my question about this on the Alloy discourse instance&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>give aliases when inverting relations</title>
		<published>2023-02-27T00:00:00+00:00</published>
		<updated>2023-02-27T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/give-aliases-when-inverting-relations/" type="text/html"/>
		<id>https://bytes.zone/posts/give-aliases-when-inverting-relations/</id>
		<content type="html">&lt;p&gt;Say you&#x27;re using &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;learning-alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; to model some tree structure. For example, a file system with directories and files:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;abstract sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  parent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: lone&lt;&#x2F;span&gt;&lt;span&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Directory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;a node cannot be its own parent&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Node &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;parent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;there is only &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;one&lt;&#x2F;span&gt;&lt;span&gt; root node&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  one&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Node &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;| no&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;parent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives us nice file system objects that always terminate in a single node and without any recursive shenanigans, like this one:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;filesystem-two-dirs-two-files.png&quot; alt=&quot;an Alloy diagram showing two directories, each containing one file. One directory also contains the other.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;However, the model currently implicitly allows this that we don&#x27;t want to include based on our mental model of the filesystem. For example, files that have other files as their parent:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;filesystem-parenting-files.png&quot; alt=&quot;an Alloy diagram showing a file containing to other files&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That doesn&#x27;t work: a directory can have children, but a file needs to have contents instead (e.g., images, text, etc.) So let&#x27;s disallow that! We&#x27;ll do that by adding a new fact like “files shouldn&#x27;t have any children.” We &lt;em&gt;could&lt;&#x2F;em&gt; do that by saying this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;files shouldn&amp;#39;t have any children&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.~&lt;&#x2F;span&gt;&lt;span&gt;parent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(In case you haven&#x27;t encountered the &lt;code&gt;~&lt;&#x2F;code&gt; operator before, it just flips the relation. That makes it child-to-parent instead of parent-to-child. More about this in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;fields-as-sets&#x2F;&quot;&gt;fields as sets&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;p&gt;Alloy can work fine with this, but in my opinion, using &lt;code&gt;~&lt;&#x2F;code&gt; like this makes the intent of the spec less clear. I always have to think about what the &lt;code&gt;~&lt;&#x2F;code&gt; is changing, even if I wrote the code not ten minutes before! Fortunately, we can clarify this by making an alias with a &lt;code&gt;fun&lt;&#x2F;code&gt; and then define our &lt;code&gt;fact&lt;&#x2F;code&gt; in those terms instead:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Node &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; Node {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  ~&lt;&#x2F;span&gt;&lt;span&gt;parent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;files shouldn&amp;#39;t have any children&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This seems much clearer to me; the assertion we&#x27;re making reads almost exactly how we&#x27;d write it in English. I like that a lot! It makes communicating about the spec easier, both for me and for anyone else who ends up reading it.&lt;&#x2F;p&gt;
&lt;p&gt;In general, I&#x27;m finding that I really like to use &lt;code&gt;fun&lt;&#x2F;code&gt;s to clarify my intent, or to put a durable label on some form of node. Doing this requires a little mindset shift from writing &lt;code&gt;pred&lt;&#x2F;code&gt;s or &lt;code&gt;fact&lt;&#x2F;code&gt;s, since &lt;code&gt;fun&lt;&#x2F;code&gt;s have to yield a subset of data instead of a boolean, but in my experience it results in higher-quality communication!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>what is the randomart image for?</title>
		<published>2023-02-20T00:00:00+00:00</published>
		<updated>2023-02-20T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/what-is-the-randomart-image-for/" type="text/html"/>
		<id>https://bytes.zone/posts/what-is-the-randomart-image-for/</id>
		<content type="html">&lt;p&gt;When you generate an SSH key (like I did when looking at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;signing-commits-with-ssh-keys&#x2F;&quot;&gt;signing commits with SSH keys&lt;&#x2F;a&gt;), you get a “randomart image” from &lt;code&gt;ssh-keygen&lt;&#x2F;code&gt;. These things:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--[ED25519 256]--+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|..o              |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|.+               |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| .E              |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|.. .     +o o    |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| .+.    S+=+     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| .... o.*+o*     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     oo*.o+oo.   |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     o o=+.oo+   |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     .o.oo==B++  |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That looks nice, and I appreciate art in my terminal, but what is it for? So, let&#x27;s see… first, I looked in &lt;code&gt;man ssh-keygen&lt;&#x2F;code&gt;, because that&#x27;s the context in which I see these, but it doesn&#x27;t have any hits for &lt;code&gt;randomart&lt;&#x2F;code&gt; or &lt;code&gt;art&lt;&#x2F;code&gt;. But on a hunch I tried &lt;code&gt;man ssh&lt;&#x2F;code&gt;, and that page has some info! Here&#x27;s what it says:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because of the difficulty of comparing host keys just by looking at fingerprint strings, there is also support to compare host keys visually, using random art. By setting the VisualHostKey option to “yes”, a small ASCII graphic gets displayed on every login to a server, no matter if the session itself is interactive or not. By learning the pattern a known server produces, a user can easily find out that the host key has changed when a completely different pattern is displayed. Because these patterns are not unambiguous however, a pattern that looks similar to the pattern remembered only gives a good probability that the host key is the same, not guaranteed proof.&lt;&#x2F;p&gt;
&lt;p&gt;To get a listing of the fingerprints along with their random art for all known hosts, the following command line can be used:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ssh-keygen -lv -f ~&#x2F;.ssh&#x2F;known_hosts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;&#x2F;blockquote&gt;
&lt;p&gt;Ok, that makes a lot of sense! There have been plenty of times when I&#x27;ve logged on to a new server, and it asks me if I want to trust key such-and-such. I mostly just say “yeah, that&#x27;s fine” without paying attention, so I see how the art could help me remember what&#x27;s right!&lt;&#x2F;p&gt;
&lt;p&gt;If I run the command given in the manual page, I get this for GitHub:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;256 SHA256:+DiY3wvvV6TuJJhbpZisF&#x2F;zLDA0zPMSvHdkr4UvCOqU github.com (ED25519)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--[ED25519 256]--+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|                 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     .           |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      o          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     o o o  .    |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     .B S oo     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     =+^ =...    |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|    oo#o@.o.     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|    E+.&amp;amp;.=o      |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|    ooo.X=.      |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+---[RSA 2048]----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| =+o...+=o..     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|o++... *o .      |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|*.o.  *o.        |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|oo.  ..o.= .     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|.+o. .. S =      |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|*=+ .  o = .     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|OE .  . o        |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| o     .         |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|                 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;256 SHA256:p2QAMXNIC1TJYWeIOttrVc98&#x2F;R1BUFWu3&#x2F;LiyKgUfQM github.com (ECDSA)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+---[ECDSA 256]---+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| .o=X*+      .o.=|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  .o=O         o |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| .  . .   E   . .|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|o     .. . .   o |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| +   . +S o.o . .|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|. . .  o++.... o.|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|   o    o.   ...+|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  o    .   o .oo.|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| .      ... o....|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The manual also mentioned that you can set &lt;code&gt;VisualHostKey&lt;&#x2F;code&gt; to &lt;code&gt;yes&lt;&#x2F;code&gt; in your SSH client config to get that information every time. If I do that, I can see that my SSH client is using the ED25519 key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ssh git@github.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Host&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF&#x2F;zLDA0zPMSvHdkr4UvCOqU&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;+--[ED25519&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 256]--+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|                 |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;     .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;           |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;      o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;          |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;     o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; o o  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;     .B&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; S oo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;     =+^ =...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    oo#o@.o.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    E+.&lt;&#x2F;span&gt;&lt;span&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;=o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    ooo.X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;PTY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; allocation request failed on channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Hi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; BrianHicks! You&amp;#39;ve successfully authenticated, but GitHub does not provide shell access.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;Connection to github.com closed.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It shows a different image when I &lt;code&gt;git push&lt;&#x2F;code&gt; to my &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;dotfiles.nix&quot;&gt;dotfiles repo&lt;&#x2F;a&gt;, which also makes sense—they&#x27;re different servers, so they have different keys. I left this on for a while and see if I started to be able to recognize the randomart images in the way the manual implies I&#x27;d be able to, and it turns out it worked! For example, I&#x27;ve started seeing the GitHub fingerprint (above) as something like the Statue of Liberty if it were a cat (don&#x27;t ask why; that&#x27;s just how my brain sees it), and the signature for &lt;code&gt;git.bytes.zone&lt;&#x2F;code&gt; (below) as the Vagrant logo. How interesting!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+---[RSA 2048]----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|.+=+.         **o|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|...+ .       *.o+|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|o + o     . = =. |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| +   . + . E . + |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  o  .+ S =   + .|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|   o .o+ . o o o |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|    ..o.+   o .  |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     o...    .   |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      oo         |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All in all, I think I&#x27;d recommend doing this. Even if the keys never change, it&#x27;s pleasing to see the art show up in your terminal. And if they do, I now have one more tool to know if something&#x27;s wrong.&lt;&#x2F;p&gt;
&lt;p&gt;One final note: I mentioned this in the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.recurse.com&#x2F;&quot;&gt;Recurse Center&lt;&#x2F;a&gt; Zulip and someone mentioned that they had recently come across &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;www.dirk-loss.de&#x2F;sshvis&#x2F;drunken_bishop.pdf&quot;&gt;the paper that describes the algorithm for making these&lt;&#x2F;a&gt;. Now we know!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>fields as sets</title>
		<published>2023-02-13T00:00:00+00:00</published>
		<updated>2023-02-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/fields-as-sets/" type="text/html"/>
		<id>https://bytes.zone/posts/fields-as-sets/</id>
		<content type="html">&lt;p&gt;Today, let&#x27;s look at how relationships between &lt;code&gt;sig&lt;&#x2F;code&gt;s in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;learning-alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; work.&lt;&#x2F;p&gt;
&lt;p&gt;Say, for example, you&#x27;re modeling database tables (&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-database-tables-in-alloy&#x2F;&quot;&gt;as I have previously&lt;&#x2F;a&gt;) and want to represent the relationship between people and their favorite flavor of ice cream:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Person&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  favoriteIceCream&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: lone&lt;&#x2F;span&gt;&lt;span&gt; Flavor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Flavor&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can ask Alloy for examples of this, and it will show you a bunch of different ways &lt;code&gt;Person&lt;&#x2F;code&gt; and &lt;code&gt;Flavor&lt;&#x2F;code&gt; can be related. For example, here&#x27;s an instance where there are three distinct people who each like a different flavor:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;three-people-three-flavors.png&quot; alt=&quot;an Alloy instance describing three people, each with a distinct favorite ice cream flavor. The people and flavors are not connected otherwise.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;favoriteIceCream&lt;&#x2F;code&gt; relationship here can be used in a bunch of interesting ways. For example, if we want to assert (contrary to our use of &lt;code&gt;lone&lt;&#x2F;code&gt;) that everybody has a favorite ice cream flavor, we&#x27;d do it like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;check NobodyDoesntLikeIceCream {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  all p: Person | some p.favoriteIceCream&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alloy finds a counterexample for this (since we used &lt;code&gt;lone&lt;&#x2F;code&gt;, which means some people don&#x27;t have a favorite flavor of ice cream), but it demonstrates the first usage of the relation: looking up fields as if they were methods or attribute accesses in an object-oriented language. &lt;code&gt;x.fieldName&lt;&#x2F;code&gt; will always work basically like you might have already intuited.&lt;&#x2F;p&gt;
&lt;p&gt;Relations can do more than this, though, since they&#x27;re actually secretly sets of tuples. If you load up the instance above in Alloy and examine it in the table view, you&#x27;ll see that it&#x27;s defined something like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;code&gt;this&#x2F;Person&lt;&#x2F;code&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;code&gt;favoriteIceCream&lt;&#x2F;code&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Person$0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Flavor$2&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Person$1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Flavor$1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Person$2&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Flavor$2&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;This looks suspiciously like a table in a database, right? Well, good news: it basically works that way too!&lt;&#x2F;p&gt;
&lt;p&gt;What &lt;code&gt;.&lt;&#x2F;code&gt; does under the hood is essentially equivalent to a SQL join: it selects rows matching a pattern on the left and gives you access to the equivalent rows on the right.&lt;&#x2F;p&gt;
&lt;p&gt;The cool thing about &lt;code&gt;.&lt;&#x2F;code&gt; is that it can do this with a set in either position. Even though it &lt;em&gt;looks like&lt;&#x2F;em&gt; you&#x27;re using a single value with &lt;code&gt;p.favoriteIceCream&lt;&#x2F;code&gt;, &lt;code&gt;p&lt;&#x2F;code&gt; is actually a subset of people—it just happens to be one with only one member.&lt;&#x2F;p&gt;
&lt;p&gt;So what other sets can you do this to? Well, if we add a &lt;code&gt;children&lt;&#x2F;code&gt; field like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sig Person {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  children: set Person,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  favoriteIceCream: lone Flavor,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We could look at some parent &lt;code&gt;p&lt;&#x2F;code&gt; and get the ice cream flavors they&#x27;d need to buy for their children with &lt;code&gt;p.children.favoriteIceCream&lt;&#x2F;code&gt;, or their grandchildren with &lt;code&gt;p.children.children.favoriteIceCream&lt;&#x2F;code&gt;. Chaining works in the way you&#x27;d expect, except with set semantics instead of having to do loops or list comprehensions.&lt;&#x2F;p&gt;
&lt;p&gt;We could also get the set of reachable ice cream flavors by using the whole set of &lt;code&gt;Person&lt;&#x2F;code&gt; on the left: &lt;code&gt;Person.favoriteIceCream&lt;&#x2F;code&gt;. We could also get the sad, lonely flavors that nobody loves with &lt;code&gt;Flavor - Person.favoriteIceCream&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s not all you can do, though. The &lt;code&gt;~&lt;&#x2F;code&gt; operator flips a relation around: if &lt;code&gt;favoriteIceCream&lt;&#x2F;code&gt; is a mapping from &lt;code&gt;Person&lt;&#x2F;code&gt; to &lt;code&gt;Flavor&lt;&#x2F;code&gt;, &lt;code&gt;~favoriteIceCream&lt;&#x2F;code&gt; is one from &lt;code&gt;Flavor&lt;&#x2F;code&gt; to &lt;code&gt;Person&lt;&#x2F;code&gt;. We can then do the same tricks as above. For example, we can find out who doesn&#x27;t like ice cream at all by doing &lt;code&gt;Person - Flavor.~favoriteIceCream&lt;&#x2F;code&gt; (since that will be all the people, minus the people who have a favorite ice cream set.)&lt;&#x2F;p&gt;
&lt;p&gt;For me, realizing that basically everything is a set, and that &lt;code&gt;~&lt;&#x2F;code&gt; and &lt;code&gt;.&lt;&#x2F;code&gt; worked set-wise unlocked a lot of uses for Alloy that I hadn&#x27;t considered before. I hope reading this helped you, too!&lt;&#x2F;p&gt;
&lt;p&gt;By the way, we haven&#x27;t even gone over all the interesting things you can do: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloy.readthedocs.io&quot;&gt;Hillel Wayne&#x27;s documentation site&lt;&#x2F;a&gt; has a big section on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloy.readthedocs.io&#x2F;en&#x2F;latest&#x2F;language&#x2F;sets-and-relations.html#sets-and-relations&quot;&gt;sets and relations&lt;&#x2F;a&gt; that&#x27;s worth a read if you&#x27;d like to learn more.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>signing commits with SSH keys</title>
		<published>2023-02-06T00:00:00+00:00</published>
		<updated>2023-02-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/signing-commits-with-ssh-keys/" type="text/html"/>
		<id>https://bytes.zone/posts/signing-commits-with-ssh-keys/</id>
		<content type="html">&lt;p&gt;I sign all my git commits. “Why” is not super important here (but basically: I participate in some open source organizations for whom supply chain security is important.) Right now, I want to focus on nice ways to make the signatures.&lt;&#x2F;p&gt;
&lt;p&gt;To start with, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;authentication&#x2F;managing-commit-signature-verification&#x2F;signing-commits&quot;&gt;here are some GitHub docs&lt;&#x2F;a&gt; on signing commits with different kinds of keys. I currently use GPG keys, but they&#x27;re kind of annoying to manage (at least for me.) A better option for me might be to sign my commits with SSH keys. But, what are the consequences of doing that?&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m gonna run some experiments to figure that out. To start with, I use two git forges: GitHub and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gitea.io&#x2F;en-us&#x2F;&quot;&gt;Gitea&lt;&#x2F;a&gt; (although I plan to migrate to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;forgejo.org&#x2F;&quot;&gt;Forgejo&lt;&#x2F;a&gt; once it&#x27;s in a stable version of nixpkgs.) I want to make sure that I can get verified commits on both.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m &lt;em&gt;most&lt;&#x2F;em&gt; interested in what happens when I revoke keys. Generally speaking, I generate one SSH key per machine I use for development. That way, no key ever has to leave the machine it was generated on. So what happens if I retire a machine, along with its key? Will all my commits turn into unsigned commits? GitHub&#x27;s documentation implies that it might be fine, but I don&#x27;t know.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-stuff-out&quot;&gt;Testing Stuff Out&lt;&#x2F;h2&gt;
&lt;p&gt;Well, then: let&#x27;s try! I&#x27;m gonna create a new repo and key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git init test-ssh-key-signing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Initialized&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; empty Git repository in test-ssh-key-signing&#x2F;.git&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; cd test-ssh-key-signing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ssh-keygen&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ed25519&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; test-key.ed25519&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Generating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; public&#x2F;private ed25519 key pair.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Enter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; passphrase&lt;&#x2F;span&gt;&lt;span&gt; (empty&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; for no passphrase&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Enter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; same passphrase again:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Your&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; identification has been saved in test-key.ed25519&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Your&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; public key has been saved in test-key.ed25519.pub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;The&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; key fingerprint is:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;SHA256:mevdSg3mn8+unpYYaB7+Sw2D6k4&#x2F;VmViamKCEHHma0M&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; brianhicks@sequoia.local&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;The&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; key&amp;#39;s randomart image is:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;+--[ED25519 256]--+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;|..o              |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;|.+               |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;| .E              |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;|.. .     +o o    |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;| .+.    S+=+     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;| .... o.*+o*     |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;|     oo*.o+oo.   |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;|     o o=+.oo+   |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;|     .o.oo==B++  |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;+----[SHA256]-----+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;First, I&#x27;ll make a commit that&#x27;s signed with my normal GPG key (which I have set globally):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --allow-empty -S -m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;this commit has been signed with my regular GPG key&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then I&#x27;ll reconfigure the repo to sign using the local key and make another commit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git config gpg.format ssh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git config user.signingKey &amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;pwd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;)&#x2F;test-key.ed25519&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --allow-empty -S -m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;this commit has been signed with the test SSH key&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I can check the signatures on the commits. Unfortunately, it looks like &lt;code&gt;git&lt;&#x2F;code&gt; needs some additional configuration to verify the signature with the SSH key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git log&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --show-signature&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;error:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 60c4afaae4bb3a2013648a26a99b994e868ee4ae&lt;&#x2F;span&gt;&lt;span&gt; (HEAD -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;No&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; signature&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Author:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Brian Hicks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;brian@brianthicks.co&lt;&#x2F;span&gt;&lt;span&gt;m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Date:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;   Thu Jan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 11:02:37&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; commit has been signed with the test SSH key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 04627098cc096584dac81c43ad70b40851880c18&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Signature made Thu Jan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 11:02:12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2023&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; CST&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;                using RSA key 66BAD9732604D23EF4A55B75C4F324B9CAAB0D50&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;                issuer &amp;quot;brian@brianthicks.com&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Good signature from &amp;quot;Brian Hicks &amp;lt;brian@brianthicks.com&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; [ultimate]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Author:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Brian Hicks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;brian@brianthicks.co&lt;&#x2F;span&gt;&lt;span&gt;m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Date:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;   Thu Jan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 11:02:12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; commit has been signed with my regular GPG key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Very well, let&#x27;s set an allowed signers file up. I found &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.gitlab.com&#x2F;ee&#x2F;user&#x2F;project&#x2F;repository&#x2F;ssh_signed_commits&#x2F;#verify-commits&quot;&gt;some instructions on GitLab to set up the allowed signers file&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; echo &amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --get&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; user.email) namespaces=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; $(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; test-key.ed25519.pub)&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; allowed_signers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git config gpg.ssh.allowedSignersFile &amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;pwd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;)&#x2F;allowed_signers&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I can get both signatures verified:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; git log&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --show-signature&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 60c4afaae4bb3a2013648a26a99b994e868ee4ae&lt;&#x2F;span&gt;&lt;span&gt; (HEAD -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Good&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;git&amp;quot; signature for brian@brianthicks.com with ED25519 key SHA256:mevdSg3mn8+unpYYaB7+Sw2D6k4&#x2F;VmViamKCEHHma0M&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Author:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Brian Hicks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;brian@brianthicks.co&lt;&#x2F;span&gt;&lt;span&gt;m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Date:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;   Thu Jan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 11:02:37&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; commit has been signed with the test SSH key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 04627098cc096584dac81c43ad70b40851880c18&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Signature made Thu Jan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 11:02:12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2023&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; CST&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;                using RSA key 66BAD9732604D23EF4A55B75C4F324B9CAAB0D50&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;                issuer &amp;quot;brian@brianthicks.com&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;gpg:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Good signature from &amp;quot;Brian Hicks &amp;lt;brian@brianthicks.com&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; [ultimate]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Author:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Brian Hicks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;brian@brianthicks.co&lt;&#x2F;span&gt;&lt;span&gt;m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Date:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;   Thu Jan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; 11:02:12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2023 -0600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; commit has been signed with my regular GPG key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Cool. Now let&#x27;s test how forges deal with rotating these keys.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;github&quot;&gt;GitHub&lt;&#x2F;h2&gt;
&lt;p&gt;GitHub says that SSH key signing is less involved but lacks features, namely that an SSH signing key can&#x27;t be revoked. Fair enough; there&#x27;s no mechanism for that. They &lt;em&gt;don&#x27;t&lt;&#x2F;em&gt; say what happens when you roll keys, though, so let&#x27;s try it.&lt;&#x2F;p&gt;
&lt;p&gt;First, I&#x27;ll push my test repo to GitHub:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; gh repo create&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --private --source&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --push&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;✓&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Created repository BrianHicks&#x2F;test-ssh-key-signing on GitHub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;✓&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Added remote https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;test-ssh-key-signing.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Enumerating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; objects: 3, done.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Counting&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; objects: 100%&lt;&#x2F;span&gt;&lt;span&gt; (3&#x2F;3), done.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Delta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; compression using up to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; threads&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Compressing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; objects: 100%&lt;&#x2F;span&gt;&lt;span&gt; (2&#x2F;2), done.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Writing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; objects: 100%&lt;&#x2F;span&gt;&lt;span&gt; (3&#x2F;3), 1.25 KiB &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; 1.25&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; MiB&#x2F;s, done.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Total&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt; (delta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;), reused 0 (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;delta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;), pack-reused 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;To&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;test-ssh-key-signing.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; [new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; branch]      HEAD&lt;&#x2F;span&gt;&lt;span&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;branch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;main&amp;#39; set up to track &amp;#39;origin&#x2F;main&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;✓&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Pushed commits to https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;test-ssh-key-signing.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I haven&#x27;t uploaded the new key, but I have enabled &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;authentication&#x2F;managing-commit-signature-verification&#x2F;displaying-verification-statuses-for-all-of-your-commits&quot;&gt;vigilant mode&lt;&#x2F;a&gt;, so their web UI shows the commit as unverified:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;unverified-commits-on-GitHub-in-the-test-ssh-key-signing-repo.png&quot; alt=&quot;the GitHub UI, showing the commit history with one verified commit signed by GPG and one unverified commit signed by SSH&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;However, if I go to my settings and add the SSH key as a signing key, it shows as verified!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;verified-commits-on-GitHub-in-the-test-ssh-key-signing-repo.png&quot; alt=&quot;the GitHub UI, showing both commits verified&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If I remove the signing key, the commit status goes back to unverified, which makes sense.&lt;&#x2F;p&gt;
&lt;p&gt;Since I uploaded this key as a signing key instead of an authentication key, it does not let me log in:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ssh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;IdentitiesOnly=yes&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; test-key.ed25519 git@github.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;git@github.com:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Permission denied&lt;&#x2F;span&gt;&lt;span&gt; (publickey).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Good! I&#x27;d be uncomfortable allowing authentication from machines with decommissioned keys, but I&#x27;m at least OK-ish with saying “the commits that I made with this are still valid forever”, as long as I can be reasonably confident that the key material is completely destroyed, so no new commits can be made. If I wanted to say &quot;any commits with signatures after this date should not be considered verified&quot;, I could use a GPG key (but you can maybe still mess around with that, since you can change the date of a commit?)&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, signing commits in this way seems like it satisfies my security requirements, so it might be possible. I still need to look at Gitea, though!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gitea-forgejo&quot;&gt;Gitea&#x2F;Forgejo&lt;&#x2F;h2&gt;
&lt;p&gt;For Gitea&#x2F;Forgejo, there&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.gitea.io&#x2F;en-us&#x2F;signing&#x2F;&quot;&gt;some documentation by Gitea&lt;&#x2F;a&gt;. It only mentions GPG keys. I wonder if it&#x27;d “just work” for SSH-based signing, though? Let&#x27;s find out!&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t have the CLI installed for Gitea, so I created a private repo in my account and pushed to it. Afterward, the commit shows up as unverified, as expected:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;unverified-commits-on-gitea-in-the-test-ssh-key-signing-repo.png&quot; alt=&quot;the Gitea UI, showing the commit history with one verified commit signed by GPG and one unverified commit signed by SSH&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I added the key to my account as a regular authentication key, but that didn&#x27;t change anything. However, I noticed a new “Verify” button in the web UI. Once I verified my SSH key (by signing a string with some instructions provided on the page) the commit showed up as verified:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;verified-commits-on-gitea-in-the-test-ssh-key-signing-repo.png&quot; alt=&quot;the Gitea UI, showing both commits verified&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This creates a problem, though: it doesn&#x27;t look like I can accept the key for signing but not authentication. For example, if I try to authenticate to my git host with the key it lets me:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ssh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;IdentitiesOnly=yes&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; test-key.ed25519 git@git.bytes.zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;PTY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; allocation request failed on channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Hi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; there, brian! You&amp;#39;ve successfully authenticated with the key named brianhicks@..., but Gitea does not provide shell access.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;If this is unexpected, please log in with password and setup Gitea under another user.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;Connection to git.bytes.zone closed.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is maybe worth asking either the Gitea or Forgejo development teams about. Of course, I could just be thinking incorrectly about the security model here: it could be the case that they don&#x27;t want to allow this because you can&#x27;t revoke SSH keys.&lt;&#x2F;p&gt;
&lt;p&gt;Either way, it seems like if I rotate my keys, I either have to allow authentication access to old keys forever, which I really don&#x27;t want to do. Looks like I&#x27;ll be sticking with my GPG key for signing commits for now!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>What does expect 1 mean in Alloy?</title>
		<published>2023-01-30T00:00:00+00:00</published>
		<updated>2023-01-30T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/what-does-expect-1-mean-in-alloy/" type="text/html"/>
		<id>https://bytes.zone/posts/what-does-expect-1-mean-in-alloy/</id>
		<content type="html">&lt;p&gt;When you open up &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;, the default &lt;code&gt;run&lt;&#x2F;code&gt; in the menu is “Run Default for 4 but 4 int, 4 seq expect 1.” I know what “for 4” means (up to 4 of every &lt;code&gt;sig&lt;&#x2F;code&gt;) and &lt;code&gt;but 4 int&lt;&#x2F;code&gt; means (4 bits worth of integers) but I don&#x27;t know what “expect 1” means!&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s look in &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;alloytools.org&#x2F;spec.html&quot;&gt;the spec&lt;&#x2F;a&gt;… nothing! It shows the “run X” and “for Y” and “but Z SigName” but not “expect.” 🤔&lt;&#x2F;p&gt;
&lt;p&gt;OK, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.discourse.group&#x2F;t&#x2F;what-does-expect-1-mean&#x2F;308&quot;&gt;on to the Alloy forums, then&lt;&#x2F;a&gt;. Daniel Jackson says it allows you to say how many results you expect. You used to be able to expect an arbitrary number, but that didn&#x27;t turn out to be useful, and now you can just say “1” (for some results) or “0” (for no results.) It&#x27;s useful for testing regressions when developing Alloy itself.&lt;&#x2F;p&gt;
&lt;p&gt;I was able to use &lt;code&gt;expect 0&lt;&#x2F;code&gt; to produce a spec that fails, as expected:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  some&lt;&#x2F;span&gt;&lt;span&gt; u&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: univ |&lt;&#x2F;span&gt;&lt;span&gt; u &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;not in univ&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt; {} &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; expect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(In English: “I want you to check that the set of all sets does not contain itself.” This is not possible in Alloy&#x27;s set semantics.)&lt;&#x2F;p&gt;
&lt;p&gt;When evaluating this, Alloy says, “No instance found. Predicate may be inconsistent, as expected.” Note the “inconsistent”, meaning “you&#x27;ve created a conflict in your expectations.” If I switch it to &lt;code&gt;expect 1&lt;&#x2F;code&gt;, it says “Predicate may be inconsistent, contrary to expectation.”&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>advice you might as well take</title>
		<published>2023-01-22T00:00:00+00:00</published>
		<updated>2023-01-22T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/advice-you-might-as-well-take/" type="text/html"/>
		<id>https://bytes.zone/posts/advice-you-might-as-well-take/</id>
		<content type="html">&lt;p&gt;I&#x27;ve read some nice articles recently which I can sum up as “advice you might as well take.” This is stuff that&#x27;s good to consider at the beginning of a project, or when you&#x27;re about to add a feature to some existing software.&lt;&#x2F;p&gt;
&lt;p&gt;These articles run counter to YAGNI (You Ain&#x27;t Gonna Need It), the software design principle that says you should only ever add things you&#x27;ll be using right away. Many even call this out in their titles! One even addresses this by coining an alternative term, PAGNI (Probably Are Gonna Need It), so let&#x27;s start with that one:&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pagnis-probably-are-gonna-need-its&quot;&gt;PAGNIs: Probably Are Gonna Need Its&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;simonwillison.net&#x2F;2021&#x2F;Jul&#x2F;1&#x2F;pagnis&#x2F;&quot;&gt;PAGNIs - Probably Are Gonna Need Its&lt;&#x2F;a&gt; by Simon Wilson. Makes a good rule for when to allow complexity in advance:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;When should you over-ride YAGNI? When the cost of adding something later is so dramatically expensive compared with the cost of adding it early on that it’s worth taking the risk. On when you know from experience that an initial investment will pay off many times over.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;His PAGNIs: make kill-switches for mobile apps to force upgrades or contain security issues, have automated deployments and continuous integration, build pagination, and keep detailed API logs, including POST bodies.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;preemptive-pluralization-is-probably-not-evil&quot;&gt;Preemptive Pluralization is Probably Not Evil&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.swyx.io&#x2F;preemptive-pluralization&quot;&gt;Preemptive Pluralization is Probably Not Evil&lt;&#x2F;a&gt; by swyx. Summed up by this quote:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before you write any code — ask if you could ever possibly want multiple kinds of the thing you are coding.&lt;&#x2F;strong&gt; If yes, just do it. Now, not later.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Basically, if:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;it&#x27;s a lot of effort to go from being able to handle zero things to being able to handle one thing&lt;&#x2F;li&gt;
&lt;li&gt;AND, it&#x27;s a similar amount of effort to go from one to many&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Then it probably makes sense to go directly from zero to many if you think that&#x27;ll ever be needed.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s also &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;wiki.c2.com&#x2F;?ZeroOneInfinityRule&quot;&gt;some interesting discussion on the C2 wiki&lt;&#x2F;a&gt;, including some times when it&#x27;s good to avoid this.&lt;&#x2F;p&gt;
&lt;p&gt;Out of these pieces of advice, this is the one I&#x27;ve used the most. However, I&#x27;d recommend modeling out your system with something like &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; first and seeing what that gets you!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;you-might-as-well-timestamp-it&quot;&gt;You Might as Well Timestamp It&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;changelog.com&#x2F;posts&#x2F;you-might-as-well-timestamp-it&quot;&gt;You might as well timestamp it&lt;&#x2F;a&gt; by Jerod Santo. Summary: if you store boolean fields as nullable timestamps, then you have basically the same semantics but get the ability to say when the value went to “true” for free.&lt;&#x2F;p&gt;
&lt;p&gt;You do use a little more disk space, but not much, so you&#x27;ll have to decide whether that&#x27;s worth it. (At least in PostgreSQL, booleans are 1 byte while a timestamp is 8.) However, I would imagine that&#x27;s fine for most uses.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;probably-are-gonna-need-it-application-security-edition&quot;&gt;Probably Are Gonna Need It: Application Security Edition&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jacobian.org&#x2F;2021&#x2F;jul&#x2F;8&#x2F;appsec-pagnis&#x2F;&quot;&gt;Probably Are Gonna Need It - Application Security Edition&lt;&#x2F;a&gt; by Jacob Kaplan-Moss. Inspired by Simon Wilson&#x27;s PAGNI post, but focused on security. There&#x27;s too much good stuff in here to summarize, but some highlights for me were to always consider the “abusive ex” persona when you&#x27;re designing your app and his thoughts on security admin interfaces.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summing-up&quot;&gt;Summing Up&lt;&#x2F;h2&gt;
&lt;p&gt;These articles show a ton of cases where it&#x27;s worth breaking YAGNI. I appreciate this because, at a higher level, I think that oversimplifying our mental model of the software we&#x27;re building can risk failure just as much as overcomplexity can. Even worse, by oversimplifying we can harm the people who use our software by failing to consider their safety as we&#x27;re building (e.g. the “abusive ex” persona.)&lt;&#x2F;p&gt;
&lt;p&gt;I want to close with a quote I really love from Hillel Wayne&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buttondown.email&#x2F;hillelwayne&#x2F;archive&#x2F;reject-simplicity-embrace-complexity&#x2F;&quot;&gt;Reject Simplicity, Embrace Complexity&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Simplicity is good. We should write simple code. But complexity is &lt;em&gt;unavoidable&lt;&#x2F;em&gt;. We do a disservice to ourselves by pretending that any software can be simple &lt;em&gt;if we just try hard enough&lt;&#x2F;em&gt;. Instead, we should study the factors that lead to complex software. That way we can learn how to recognize, predict, and manage complexity in our systems. And then we can seek simplicity within that context. It won’t give us simple software, but it will help us write &lt;em&gt;simpler&lt;&#x2F;em&gt; software. Nuance is better than mantras.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>pagerank for my Obsidian notes</title>
		<published>2023-01-17T00:00:00+00:00</published>
		<updated>2023-01-17T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/pagerank-for-my-obsidian-notes/" type="text/html"/>
		<id>https://bytes.zone/posts/pagerank-for-my-obsidian-notes/</id>
		<content type="html">&lt;p&gt;I recently learned a little more about how the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;PageRank&quot;&gt;Pagerank&lt;&#x2F;a&gt; algorithm works. I thought it was cool, and it made me wonder what the ranks for my &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;obsidian.md&quot;&gt;Obsidian&lt;&#x2F;a&gt; vault looked like, so I built a little tool. It&#x27;s fairly simple: it just parses &lt;code&gt;[[links]]&lt;&#x2F;code&gt; out of the vault with a regex and then uses the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;simple-pagerank&quot;&gt;simple-pagerank&lt;&#x2F;a&gt; crate for Rust to calculate the ranking.&lt;&#x2F;p&gt;
&lt;p&gt;For my vault, the most centrally connected node is my employer. It&#x27;s the top node by quite a lot (with a score of 4.9), followed by the town where I live (2.0), then members of my family (around 2) and notes on projects I&#x27;m doing and tools I use (around 1.) This makes a lot of sense to me; I primarily use Obsidian as a way to keep a work log and write about things related to that, &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;digital-gardening-in-obsidian&#x2F;&quot;&gt;as I wrote about previously&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d like to try this for yourself, you can get the code at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;obsidian-pagerank&quot;&gt;bytes.zone&#x2F;brian&#x2F;obsidian-pagerank&lt;&#x2F;a&gt;, although you&#x27;ll need to have either &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nixos.org&quot;&gt;Nix&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;&quot;&gt;Rust&lt;&#x2F;a&gt; installed in advance. If you decide to try it, let me know if you find anything interesting!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>The Value of a Model is More Making than Having</title>
		<published>2023-01-09T00:00:00+00:00</published>
		<updated>2023-01-09T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/the-value-of-a-model-is-more-making-than-having/" type="text/html"/>
		<id>https://bytes.zone/posts/the-value-of-a-model-is-more-making-than-having/</id>
		<content type="html">&lt;p&gt;After making a bunch of models in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt;, I&#x27;m curious if formal modeling is more useful as a thing to do (a process) or a thing to create (an artifact.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;making-process&quot;&gt;Making &#x2F; Process&lt;&#x2F;h2&gt;
&lt;p&gt;On the modeling-as-a-process side, I&#x27;ve had good luck taking the time to carefully model a system and asking domain experts how the model is wrong. In those cases, I tend to get one of two responses:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;“Oh, we take care of that case with X” (which means I need to change my model)&lt;&#x2F;li&gt;
&lt;li&gt;“Uh-oh, we hadn&#x27;t thought of that” (which means we might need to fix bugs or make other changes to the system in question)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For example, I recently modeled an upcoming feature my team would be working on and found several problems (in this case, actions we hadn&#x27;t disallowed that would violate our assumptions about what data would be shown on the page.) I brought those to the folks in charge of the spec, and it turned out they had considered several—but not all—of them and that they had already decided that the consequences were acceptable. I&#x27;m still glad I did it, though, since it gave me a much better sense of what we were building and the consequences of our approach.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve also found it useful to sit down with other people and have a conversation &lt;em&gt;using&lt;&#x2F;em&gt; formal modeling; the model acts as a conversation partner to keep us from assuming different things about the world and point out problems in our thinking. This has even been useful when one or more of the humans involved hasn&#x27;t known about formal modeling in advance because I can explain stuff on the fly. For example, as I wrote &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;previously&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;For example, at work we were building a feature with inline commenting (like Google Docs.) We started by modeling Google Docs&#x27; comments and it turned out that each comment had something like 32 unique states. We realized this was going to be way too much for our use case, and simplified it down to 4. We probably could have had this realization without using Alloy, but it made the conversation much simpler—the designer and I sat down and looked at the visualizations, then kept saying &quot;but for us, states X and Y would be the same…&quot; until we arrived at a simpler model. It was really nice, and only took about an hour and a half of modeling to come up with a solution that satisfied all our requirements!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This is basically “&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Formal_methods#Lightweight_formal_methods&quot;&gt;lightweight formal methods&lt;&#x2F;a&gt;”—we&#x27;re not out to solve the Byzantine generals problem or whatever, but just to make sure there aren&#x27;t any big issues we hadn&#x27;t thought of or flaws we can&#x27;t live with.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;having-artifact&quot;&gt;Having &#x2F; Artifact&lt;&#x2F;h2&gt;
&lt;p&gt;On the models-as-artifacts side, I&#x27;ve had some success, but less than modeling-as-a-process.&lt;&#x2F;p&gt;
&lt;p&gt;To start, I&#x27;ve tried to turn Alloy and TLA+ models into documentation. Alloy&#x27;s ability to generate examples and its literate markdown support is amazing, but both still require the reader to have some basic familiarity with Alloy&#x27;s syntax and how to interpret the visualizations.&lt;&#x2F;p&gt;
&lt;p&gt;This can work really well for tutorials and explorations, though: &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;modeling-database-tables-in-alloy&#x2F;&quot;&gt;modeling database tables in Alloy&lt;&#x2F;a&gt; was originally written as a literate Alloy file (and still lives in my notes in a form that Alloy could pick up and run.) At work, we now have a few documentation files explaining different features and referencing a common tutorial I wrote for background. It works alright, although I&#x27;m not sure how many people have read them (but I have sent links to folks who wanted a refresher on the features, which seems to have worked.)&lt;&#x2F;p&gt;
&lt;p&gt;Beyond documentation of fairly static features, the model and the actual system almost always diverge. People add features and fix bugs in ways that violate the assumptions or assertions of the model, at which point the model is making strong claims that don&#x27;t match up with real life.&lt;&#x2F;p&gt;
&lt;p&gt;The weird thing, though, is that &lt;strong&gt;this is something you want&lt;&#x2F;strong&gt;. It&#x27;s not useful to specify your model all the way down to the level of your code. It can be distracting to have random states changing that don&#x27;t matter for the thing you&#x27;re trying to assert about the model. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Map%E2%80%93territory_relation&quot;&gt;The map is not the territory&lt;&#x2F;a&gt;, after all! For example, your real-life HTTP service always has to deal with network problems, but your model might not need to. It depends on what you&#x27;re trying to figure out about the system, and that might change over time as you make models of different parts of the system!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;so-which-is-it&quot;&gt;So which is it?&lt;&#x2F;h2&gt;
&lt;p&gt;Based on my experiences with formal modeling tools, I think the process of creating a model is more valuable than the thing you produce. I&#x27;ve seen fewer drawbacks to approaching modeling as a process or as a communication tool without worrying about preserving the model for the long term.&lt;&#x2F;p&gt;
&lt;p&gt;However, my opinion here may change as I practice more. I&#x27;ve already produced a few models-as-documentation for stabler and longer-lived parts of systems at work, and those have been helpful to introduce people to the concepts involved in an isolated way. Maybe trying to find more ways that models have long-term value will change my mind here!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Thank you to Ezzeri Esa and Miccah Castorina at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;recurse.com&quot;&gt;the Recurse Center&lt;&#x2F;a&gt; for reviewing a draft of this post!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Modeling Database Tables in Alloy</title>
		<published>2023-01-02T00:00:00+00:00</published>
		<updated>2023-01-02T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/modeling-database-tables-in-alloy/" type="text/html"/>
		<id>https://bytes.zone/posts/modeling-database-tables-in-alloy/</id>
		<content type="html">&lt;p&gt;Formal methods tools like Alloy are not just for proving correctness properties in distributed systems (or whatever) but can be a useful tool for mere mortals like me! &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;alloy&#x2F;&quot;&gt;As I mentioned previously&lt;&#x2F;a&gt;, I&#x27;ve been using Alloy to help me avoid data modeling mistakes before they&#x27;re encoded in hard-to-change places, like database schemas. In this post, I&#x27;ll show my thought process as I use Alloy to model the schema for a project for my makerspace, including some of the mistakes that Alloy helped me avoid.&lt;&#x2F;p&gt;
&lt;p&gt;I joined the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.inventorforgemakerspace.org&#x2F;&quot;&gt;Inventor Forge Makerspace&lt;&#x2F;a&gt; recently to get access to some better tools for &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;home-recycling&#x2F;&quot;&gt;home recycling&lt;&#x2F;a&gt;. There are some cool tools there (Lasers! CNC machines! Plasma cutters!) that need both special training and have high demand among the folks who use the space. Right now, we use a shared Google calendar to schedule time on different machines. Unfortunately, I don&#x27;t have a personal Google account, so it&#x27;s harder to schedule than I&#x27;d like! There are a couple of other people in this situation too, so I&#x27;m in the process of building a tool to track training and scheduling.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ll start by modeling &lt;code&gt;Member&lt;&#x2F;code&gt; (someone who can join the makerspace) and a &lt;code&gt;Tool&lt;&#x2F;code&gt; (something that we need training and scheduling for.) We&#x27;ll use Alloy&#x27;s basic building block of &lt;code&gt;sig&lt;&#x2F;code&gt;s for this. If you haven&#x27;t been exposed to Alloy before, you can think of &lt;code&gt;sig&lt;&#x2F;code&gt;s as sets that can contain values (like rows in a database table.) So you can think of &lt;code&gt;Member&lt;&#x2F;code&gt; as the set of all members of the makerspace, which can contain values like &lt;code&gt;Member0&lt;&#x2F;code&gt; and &lt;code&gt;Member1&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Member&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Tool&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can ask Alloy to generate examples for this small model. They won&#x27;t be super useful at this point—we haven&#x27;t modeled any interactions between &lt;code&gt;Member&lt;&#x2F;code&gt; and &lt;code&gt;Tool&lt;&#x2F;code&gt;—but they let us get a sense of what Alloy can provide us. For example, here&#x27;s an instance with two members and two tools:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;two-members-two-tools.png&quot; alt=&quot;two members and two tools, not connected in any way&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We can ask Alloy for any number of instances, and we&#x27;ll be looking at these frequently to find the kinds of edge cases we&#x27;re interested in.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connecting-members-and-tools&quot;&gt;Connecting Members and Tools&lt;&#x2F;h2&gt;
&lt;p&gt;To get authorization to use a tool at my makerspace, you need to contact a &quot;tool champion&quot; who is responsible for doing safety training. (They&#x27;re not just &quot;trainers&quot; because they also maintain, fix, and upgrade the tools.) In our model, we&#x27;ll first connect &lt;code&gt;Member&lt;&#x2F;code&gt; and &lt;code&gt;Tool&lt;&#x2F;code&gt; by adding a new &lt;code&gt;sig Champion&lt;&#x2F;code&gt;, which has a relationship with both.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Champion&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Tool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  member&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Member&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When we&#x27;re turning this model into a real database, &lt;code&gt;Champion&lt;&#x2F;code&gt; will become a &lt;code&gt;champions&lt;&#x2F;code&gt; table, with foreign keys to &lt;code&gt;tools&lt;&#x2F;code&gt; (&lt;code&gt;tool_id&lt;&#x2F;code&gt;) and &lt;code&gt;members&lt;&#x2F;code&gt; (&lt;code&gt;member_id&lt;&#x2F;code&gt;.)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-you-be-the-champion-for-more-than-one-tool-at-once&quot;&gt;Can you be the champion for more than one tool at once?&lt;&#x2F;h3&gt;
&lt;p&gt;Now that we have some relationships, we can poke around in the instances Alloy generates to see if we can find anything that feels off. For example, Alloy shows that we&#x27;re allowing duplicate champions for the same member and tool:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;one-member-four-championships.png&quot; alt=&quot;one instance of member and one of tool connected by four separate champion instances&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This could cause us problems later. Imagine what could happen if we also had a boolean field named &lt;code&gt;active&lt;&#x2F;code&gt; and a champion resigned. We could end up with one row that says they&#x27;re active and another that says they&#x27;re inactive. We&#x27;d have to define some conflict semantics in the application, making sure to only change the one we care most about, or all of them, or whatever. But we can get around all that by disallowing these duplicate rows!&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, this is fairly simple to achieve in a database schema by adding a unique index on &lt;code&gt;champions.tool_id&lt;&#x2F;code&gt; and &lt;code&gt;champions.member_id&lt;&#x2F;code&gt;. We can do something similar in our Alloy model by introducing a &lt;code&gt;fact&lt;&#x2F;code&gt; (a logical statement that Alloy will assume is always correct.)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;a unique index on champions prevents duplicate member&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;tool combinations&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Tool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Member &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;| lone&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Champion &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;member &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; m&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In English, this is saying &quot;for every combination of &lt;code&gt;Tool&lt;&#x2F;code&gt; and &lt;code&gt;Member&lt;&#x2F;code&gt;, there&#x27;s at most one &lt;code&gt;Champion&lt;&#x2F;code&gt; that connects them.&quot; (The mnemonic for &lt;code&gt;lone&lt;&#x2F;code&gt; is &quot;less than or equal to one&quot;. In this case, you can think of it as &quot;if there&#x27;s one, there&#x27;s &lt;em&gt;only&lt;&#x2F;em&gt; one.&quot;)&lt;&#x2F;p&gt;
&lt;p&gt;Just to emphasize, Alloy will take our word that this fact is always true in our system instead of checking it for us! Because of this, I like to include some justification in the description for how this is true in the system I&#x27;m modeling. In this case, that means explaining the mechanism for how we&#x27;re going to make sure this property holds: the unique index.&lt;&#x2F;p&gt;
&lt;p&gt;To make sure we&#x27;ve got this completely covered, we&#x27;ll also assert that we solved the problem. In this case, we&#x27;ll assert that the case we found earlier can&#x27;t come up because of our new fact:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; NoDuplicateChampions&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; disjoint c1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; c2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Champion &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; c1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; c2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; c1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;member &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; c2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;member&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We need &lt;code&gt;disjoint&lt;&#x2F;code&gt; here because, otherwise, Alloy could choose the same &lt;code&gt;Champion&lt;&#x2F;code&gt; as both &lt;code&gt;c1&lt;&#x2F;code&gt; and &lt;code&gt;c2&lt;&#x2F;code&gt;, in which case the assertion would be trivially disprovable—two references to the same champion would naturally have the same tool and member.&lt;&#x2F;p&gt;
&lt;p&gt;Alloy does not find any counterexamples to this check, so we&#x27;re safe to continue.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trainings&quot;&gt;Trainings&lt;&#x2F;h2&gt;
&lt;p&gt;Now that we have champions members to join the space, tools for them to use, and champions to train them… how do we get access to the tools? Trainings! Someone can have access to a tool that they&#x27;ve trained for, so we can represent it as another &lt;code&gt;sig&lt;&#x2F;code&gt;&#x2F;table:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Training&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  trainer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Champion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  trainee&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Member&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;ll add another validation, as well: that the trainer and trainee can&#x27;t be the same person. We may be able to do some index trickery to get this to work, but I think it&#x27;s more likely that we&#x27;ll have to validate this at the app level.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;the application code validates that trainer &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;!=&lt;&#x2F;span&gt;&lt;span&gt; trainee&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Training &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;member &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainee&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Including this justification makes me wonder what the application should do if someone inserts a row that violates this fact into &lt;code&gt;trainings&lt;&#x2F;code&gt; directly, but I&#x27;m going to leave that for the moment.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;examining-more-instances&quot;&gt;Examining More Instances&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s look at the model instances to see if there&#x27;s anything worth fixing now. Fortunately, there&#x27;s not a lot: we&#x27;ve already ruled out quite a few problems with our modeling so far. That means we&#x27;ll get mostly &quot;normal&quot; instances. For example, here&#x27;s what it looks like when a champion has trained someone on a tool:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;champion-training-member.png&quot; alt=&quot;a complete training, showing a tool, the champion for that tool, another member, and the training instance for that member&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A champion can also train two members on the same tool. Also fine:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;champion-training-two-members.png&quot; alt=&quot;two complete trainings, showing a tool, the champion for that tool, and two members, each with training from the champion&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;But Alloy also gives us this, in which the same member is trained multiple times:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;training-twice.png&quot; alt=&quot;two training instances for the same member. The champion and tool are the same for both.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Thinking through our domain, this &lt;em&gt;might&lt;&#x2F;em&gt; be ok. Because the training is for safety, it&#x27;s not uncommon for someone to request to have a refresher on a tool if it&#x27;s been a while since they&#x27;ve used it. This is especially common with the woodworking tools, where there are a lot of sharp things near your hands. However, is it OK in our database schema, or would we rather disallow duplicates and (for example) update the date of the training if someone re-trains? I&#x27;ll have to talk to the person in charge of our current record-keeping system to find out!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reservations&quot;&gt;Reservations&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ve already found a couple of interesting questions in our modeling; let&#x27;s see if we can find more by modeling reservations on machines (finally, we get to the reason I&#x27;m doing this in the first place!)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Reservation&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  start&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Training&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;} {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  -- a CHECK enforces ordering in the database&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  start &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;ll use &lt;code&gt;Int&lt;&#x2F;code&gt;s to specify the start and end times of the reservation and specify that they have to be ordered. One weird corner I&#x27;m noting about this design is that since I&#x27;ve used &lt;code&gt;Training&lt;&#x2F;code&gt; as the authorization (just to say that you can&#x27;t get time on a tool you haven&#x27;t been trained on) you have to access the tool in a roundabout way: given a &lt;code&gt;Reservation&lt;&#x2F;code&gt; named &lt;code&gt;r&lt;&#x2F;code&gt;, it&#x27;s &lt;code&gt;r.auth.trainer.tool&lt;&#x2F;code&gt;. Could be worse, but that&#x27;s gonna be a lot of joins in the database. There are a couple of alternatives here:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;we could reference &lt;code&gt;Tool&lt;&#x2F;code&gt; directly from &lt;code&gt;Reservation&lt;&#x2F;code&gt;, but then we could then construct a &lt;code&gt;Reservation&lt;&#x2F;code&gt; for a tool that a member hasn&#x27;t been trained on using a &lt;code&gt;Training&lt;&#x2F;code&gt; for another tool. We&#x27;d have to enforce that in the app, and I&#x27;d prefer not to—the database should take care of data integrity, where possible.&lt;&#x2F;li&gt;
&lt;li&gt;we could embed a &lt;code&gt;Tool&lt;&#x2F;code&gt; in &lt;code&gt;Training&lt;&#x2F;code&gt; as part of the foreign key to &lt;code&gt;Champion&lt;&#x2F;code&gt;, then do the same in &lt;code&gt;Reservation&lt;&#x2F;code&gt;. This would be a bunch of coordination work, but might be nicer: it&#x27;d mean that we couldn&#x27;t change &lt;code&gt;Champion.tool&lt;&#x2F;code&gt; without figuring out how to update &lt;code&gt;Training&lt;&#x2F;code&gt; and &lt;code&gt;Reservation&lt;&#x2F;code&gt; at the same time. It&#x27;d take more coordination, but it&#x27;d give our data model some nice additional robustness.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;m partial to adding the foreign keys from option 2, but for the sake of completing our model (and this post) let&#x27;s move on.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;preventing-conflicting-reservations&quot;&gt;Preventing Conflicting Reservations&lt;&#x2F;h3&gt;
&lt;p&gt;To finish the &lt;code&gt;reservations&lt;&#x2F;code&gt; table, we should rule out overlapping reservations for the same tool. To start with, we can assert (incorrectly) that reservations already can&#x27;t overlap to get Alloy to give us a counterexample:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; reservedTimes&lt;&#x2F;span&gt;&lt;span&gt;[r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Reservation]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Int {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;start&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.*&lt;&#x2F;span&gt;&lt;span&gt;next &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.^&lt;&#x2F;span&gt;&lt;span&gt;prev&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; NoConflictingReservations&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; disjoint r1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Reservation &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    r1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      implies no&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; reservedTimes&lt;&#x2F;span&gt;&lt;span&gt;[r1] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; reservedTimes&lt;&#x2F;span&gt;&lt;span&gt;[r2]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The way to read this is &quot;given two different &lt;code&gt;Reservation&lt;&#x2F;code&gt;s with the same tool, there should be no overlap in their scheduling.&quot; The &lt;code&gt;implies&lt;&#x2F;code&gt; there works like a conditional statement in an imperative language: in this case, you can read it as &quot;if X then Y&quot; instead of &quot;X implies Y,&quot; so &quot;if the tools are the same, the times can&#x27;t overlap.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;next&lt;&#x2F;code&gt; and &lt;code&gt;prev&lt;&#x2F;code&gt; relations on &lt;code&gt;start&lt;&#x2F;code&gt; and &lt;code&gt;end&lt;&#x2F;code&gt; are new as well—they come from Alloy&#x27;s ordered set support and implicitly exist on ordered sets like &lt;code&gt;Int&lt;&#x2F;code&gt;. They let you do things like calling &lt;code&gt;1.next&lt;&#x2F;code&gt; to get &lt;code&gt;2&lt;&#x2F;code&gt;. You can use the recursion operators here (another new concept to us,) where &lt;code&gt;*&lt;&#x2F;code&gt; will repeat a lookup zero or more times, and &lt;code&gt;^&lt;&#x2F;code&gt; will repeat a lookup one or more times. That means that calling &lt;code&gt;r.start.*next&lt;&#x2F;code&gt; gives us &lt;code&gt;r.start&lt;&#x2F;code&gt; plus all the times after it, and &lt;code&gt;r.end.^prev&lt;&#x2F;code&gt; gives us all times before the end, not including &lt;code&gt;r.end&lt;&#x2F;code&gt; (since one reservation should be able to start exactly when another ends.)&lt;&#x2F;p&gt;
&lt;p&gt;If we find the set intersection of those two lookups (the middle of a Venn diagram), we get a range, so saying &lt;code&gt;r.start.*next &amp;amp; r.end.^prev&lt;&#x2F;code&gt; gets us the range of reserved times for &lt;code&gt;r&lt;&#x2F;code&gt;. We can use this (in &lt;code&gt;reservedTimes&lt;&#x2F;code&gt;) to make assertions that there&#x27;s no overlap between the two reservations.&lt;&#x2F;p&gt;
&lt;p&gt;As expected, Alloy finds a violation of this check more-or-less instantly:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;conflicting-reservation.png&quot; alt=&quot;two reservations instances for the same member on the same tool. One starts at time -6 and ends at time 4. The other starts at time 0 and ends at time 6.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this case, the same member is making both reservations. I&#x27;m not convinced that that&#x27;s OK, but we&#x27;ll leave it alone for now and focus on the fact that &lt;code&gt;r2&lt;&#x2F;code&gt; starts at time &lt;code&gt;0&lt;&#x2F;code&gt; before &lt;code&gt;r1&lt;&#x2F;code&gt; ends at &lt;code&gt;4&lt;&#x2F;code&gt;. It looks like we can &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;15&#x2F;rangetypes.html#id-1.5.7.25.16.2&quot;&gt;use a GiST index in PostgreSQL to exclude overlapping ranges&lt;&#x2F;a&gt;. In fact, the last example on that page shows how you can avoid conflicts for a meeting room, which is similar to our example!&lt;&#x2F;p&gt;
&lt;p&gt;So, for now, let&#x27;s introduce a new fact with the constraint that would be introduced by adding such an index:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;a GiST index prevents overlapping ranges&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; disjoint r1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Reservation &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (r1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tool &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; r1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;start &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt; r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;start)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      implies&lt;&#x2F;span&gt;&lt;span&gt; r1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;end &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt; r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;start&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We only have to make one assertion here (that &lt;code&gt;r1&lt;&#x2F;code&gt; ends before &lt;code&gt;r2&lt;&#x2F;code&gt; starts) because Alloy will check every possible combination of &lt;code&gt;Reservation&lt;&#x2F;code&gt; (that&#x27;s what &lt;code&gt;all&lt;&#x2F;code&gt; does.) That means that every reservation will eventually be checked as both &lt;code&gt;r1&lt;&#x2F;code&gt; and &lt;code&gt;r2&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, Alloy now says that it can&#x27;t find any counterexamples, which is what we want.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;browsing-instances-again&quot;&gt;Browsing Instances Again&lt;&#x2F;h2&gt;
&lt;p&gt;I &lt;em&gt;think&lt;&#x2F;em&gt; we&#x27;re done now, but I&#x27;m just going to browse a couple more examples to make sure. (I do this a lot.) First, we get a completely normal reservation:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;normal-reservation.png&quot; alt=&quot;a complete reservation from time 5 to time 6 using a single training, non-champion member, champion, and tool&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;No conflicts and the structure looks OK to me! Next, we have one reservation starting as soon as the previous one ends:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;starting-and-ending-at-the-same-time.png&quot; alt=&quot;two complete reservations for the same tool, using the same training, member, champion, and tool. One starts at the same time the other ends.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This one&#x27;s a little funny since someone is using two different training authorizations. This is the kind of example I&#x27;ll have to bring up when I talk with the person who keeps the records now. Having a diagram like this can help explain the case I&#x27;m thinking about to someone else, so I&#x27;m glad I found it. I&#x27;m also going to ask whether it&#x27;s OK for one member to have two consecutive reservations—I don&#x27;t know!&lt;&#x2F;p&gt;
&lt;p&gt;I also notice we&#x27;re not getting any instances where the champion can schedule a reservation, but that&#x27;s something that should be able to happen. We can get Alloy to show us if it&#x27;s possible or not by asserting it can&#x27;t happen and then looking at the counterexample:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; ChampionCantSchedule&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  no&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Champion&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Reservation &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;trainee &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;member&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alloy comes up with this. A champion can be trained by another champion, at which point they can make reservations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;champion-cant-schedule.png&quot; alt=&quot;a reservation on a tool for a champion. The champion has been trained by another champion, allowing the reservation to exist through that training.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That seems reasonable, but how do you bootstrap it? Maybe &lt;code&gt;Champion&lt;&#x2F;code&gt; actually needs to be a subset of &lt;code&gt;Training&lt;&#x2F;code&gt; (that is, a boolean field on the &lt;code&gt;training&lt;&#x2F;code&gt; table?) Or maybe we should relax the requirement that a champion can&#x27;t train themselves? This is an edge case that I might not have thought of otherwise!&lt;&#x2F;p&gt;
&lt;p&gt;But let&#x27;s leave this here for now. With only a page or so of Alloy code, we&#x27;ve chased out a bunch of potential bugs in our model and found some interesting jumping-off points for questions while I continue to develop the model into an actual application. This has been pretty typical of my experience using Alloy in this way: in all but the simplest cases, it finds questions that I need to answer with very little effort on my part.&lt;&#x2F;p&gt;
&lt;p&gt;Hopefully, at this point, you&#x27;re interested and want to learn more about Alloy. Here are some good links:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.org&#x2F;&quot;&gt;The Alloy project site&lt;&#x2F;a&gt; (where you can get the software and some older learning material)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hillelwayne.com&#x2F;post&#x2F;alloy6&#x2F;&quot;&gt;Hillel Wayne&#x27;s introduction to Alloy 6&lt;&#x2F;a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloy.readthedocs.io&#x2F;en&#x2F;latest&#x2F;&quot;&gt;his reference docs mostly covering Alloy 5&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;haslab.github.io&#x2F;formal-software-design&#x2F;&quot;&gt;A draft book about designing software in Alloy 6&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In general, most learning material does not cover Alloy 6, which came out about a year ago as of this writing. The links above are the only things I&#x27;ve found that do, although there may be more by the time you&#x27;re reading this!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Thanks to Hazem at the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;recurse.com&quot;&gt;Recurse Center&lt;&#x2F;a&gt; and Tessa Kelly at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;noredink.com&quot;&gt;NoRedInk&lt;&#x2F;a&gt; for reviewing drafts of this post!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Digital Gardening in Obsidian</title>
		<published>2022-12-26T00:00:00+00:00</published>
		<updated>2022-12-26T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/digital-gardening-in-obsidian/" type="text/html"/>
		<id>https://bytes.zone/posts/digital-gardening-in-obsidian/</id>
		<content type="html">&lt;p&gt;I have been using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;obsidian.md&quot;&gt;Obsidian&lt;&#x2F;a&gt; for all my notes for something like 6 months now. That&#x27;s really &lt;em&gt;everything&lt;&#x2F;em&gt;, from temporary measurements to journaling to serious documentation work. I&#x27;ve tried Obsidian a couple of times before and bounced off of it, so I decided to try to do things differently this time. Instead of starting out by figuring out a system for where everything lives in advance, I&#x27;ve just thrown almost everything in the default folder and sorted it out with linking and search.&lt;&#x2F;p&gt;
&lt;p&gt;Some structure has emerged over time, which I&#x27;m fairly happy with, but it&#x27;s more to do with practices than things that would make it easier for someone else to read my vault. These practices basically boil down to &quot;digital gardening&quot;—they allow me to keep things neat and tidy and observe growth without being overwhelmed by it. Here&#x27;s some of what I do!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rollup-journaling&quot;&gt;Rollup Journaling&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve been keeping a log in my daily notes. These logs can contain answers to journal prompts like &quot;what have I been avoiding?&quot; but most of the time it&#x27;s just a roughly accurate timestamped log of what I&#x27;ve been up to in a given day.&lt;&#x2F;p&gt;
&lt;p&gt;Every Monday I summarize these daily notes into a weekly digest. These have more of a journaling flavor to them, since one of the goals is to help me discover patterns, but the template is still pretty basic: a header for highlights, one for lowlights, and one for things I want to change in the next week.&lt;&#x2F;p&gt;
&lt;p&gt;If the particular Monday I&#x27;m doing this on also starts a new month, I&#x27;ll also do a summary of the weeks in the previous month following the same basic pattern. Same for the last 3 months of monthly notes, if it happens to be the first Monday of April, July, October, or January. I haven&#x27;t been doing this long enough to do a yearly rollup yet, but I may do a 2022 yearly rollup note in January.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been really happy with this! The big benefit so far I&#x27;ve seen is being able to see back in time with more clarity. There have been times this year where I&#x27;ve felt stuck—like I&#x27;m not making progress and haven&#x27;t for a long time—but looking back in my notes I can see that I actually have done some pretty major things in the past year. This has had a nice side benefit that my end-of-year review at work was really easy to write: I just looked back at my quarterly reviews, as well as a few monthly ones, and pulled out some good stuff. It doesn&#x27;t completely get rid of recency bias, but it does mean that I&#x27;m not &lt;em&gt;only&lt;&#x2F;em&gt; focusing on stuff that happened in the last month or two.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t know if &quot;rollup journaling&quot; is the best name for this, but it&#x27;s one that&#x27;s stuck with me for some reason. I got the idea for doing this a couple years (and tries at Obsidian) ago based on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=AzrEDnIye14&quot;&gt;Britney Braxton&#x27;s talk at JuneteenthConf 2020, &lt;em&gt;Journaling as a Dev&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;spaced-repetition&quot;&gt;Spaced Repetition&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve been using the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;st3v3nmw&#x2F;obsidian-spaced-repetition&quot;&gt;community spaced repetition plugin&lt;&#x2F;a&gt; to periodically review notes. It&#x27;s a bit of a hack—this plugin is clearly not meant to be used like this—but it works.&lt;&#x2F;p&gt;
&lt;p&gt;I add a &lt;code&gt;#review&lt;&#x2F;code&gt; tag to every note that I want to periodically come back to and improve or tweak (this is actually most of my notes.) I then ask the spaced repetition plugin if there&#x27;s anything for me to look at. If I make any changes, I&#x27;ll mark the review as &quot;hard&quot; so the plugin will show it to me earlier. If I don&#x27;t think it needs any changes, I&#x27;ll mark it as &quot;easy&quot; to send it further into the future.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not terribly disciplined about doing this every day, but it has a nice benefit of improving notes that need to improve while sending ones that don&#x27;t into the far future for me to get reminded of periodically.&lt;&#x2F;p&gt;
&lt;p&gt;I think I originally got the idea for this from an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;notes.andymatuschak.org&#x2F;z36iMKLe4CDAXdtLSJD4Z6qPPFUS8ZXymUk3i&quot;&gt;Andy Matuschak note&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pages-that-don-t-exist-but-should&quot;&gt;Pages that Don&#x27;t Exist, but Should&lt;&#x2F;h2&gt;
&lt;p&gt;When writing notes, I link freely, even if the page I&#x27;m linking to doesn&#x27;t exist yet. Then, every couple of days, I&#x27;ll have a look at my &quot;pages that don&#x27;t exist but should&quot; note, which has a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blacksmithgu.github.io&#x2F;obsidian-dataview&#x2F;&quot;&gt;dataview&lt;&#x2F;a&gt; query that shows me all those nonexistent pages, ranked by how many incoming links they would have if they did exist.&lt;&#x2F;p&gt;
&lt;p&gt;For example, if I look at that page right now, I can see that I haven&#x27;t made a note for Phoenix Liveview—I&#x27;ve been looking into it for a side project and have made notes on a couple of conference talks where I&#x27;ve linked to it. There&#x27;s also a missing note for some features of the product I work on, since I&#x27;ve mentioned them in daily notes.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m fairly happy with this approach. It&#x27;s a bit easier than looking for greyed-out nodes in the graph view, and over time it&#x27;s lead to some interesting practices. For example, I started linking the names of my coworkers when I paired or collaborated with them on something, which eventually lead to keeping notes on what other teams around the company are responsible for. I&#x27;m not sure I would have started that (quite useful) practice if I hadn&#x27;t been paying attention to what &quot;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Desire_path&quot;&gt;desire paths&lt;&#x2F;a&gt;&quot; I was creating in my vault.&lt;&#x2F;p&gt;
&lt;p&gt;In case it&#x27;s useful to someone else, here&#x27;s the dataview script I embed for this. There&#x27;s probably a better way to get this information, but it&#x27;s been working well as-is. This version filters out daily notes and doesn&#x27;t consider media files, but you may want to remove those lines if being prompted to create daily notes far in the past or future sounds useful to you:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; phantoms&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; dv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;pages&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;flatMap&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; p.file.outlinks.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;link&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [link, p.file]))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;groupBy&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; p[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; g.rows.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;endsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;.png&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;endsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;.jpeg&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;endsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;.jpg&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;endsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;.mov&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d\d\d\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBEDFF;&quot;&gt;-Q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d\d\d\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBEDFF;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBEDFF;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      !&lt;&#x2F;span&gt;&lt;span&gt;g.key.path.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d\d\d\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBEDFF;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\d\d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; { key: g.key, rows: g.rows.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; r[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]) };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  })&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; dv.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;page&lt;&#x2F;span&gt;&lt;span&gt;(g.key)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;sort&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; g.rows.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;desc&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dv.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;table&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Page to Create&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Linked From&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  phantoms.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [g.key, g.rows.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; p.link)]),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;other-dataview-shenanigans&quot;&gt;Other Dataview Shenanigans&lt;&#x2F;h2&gt;
&lt;p&gt;Other than the stuff mentioned above, I have a handful of useful dataview pages. For example, I&#x27;m looking into replacing an older car we own right now. The page for that project has a dataview query summing up research I&#x27;ve done into cars I might want to buy to replace it. I also have a view of everything tagged &lt;code&gt;#possible-blog-post&lt;&#x2F;code&gt; sorted by when I think I might want to publish it. None of these are huge, but they do a good job of gathering information from around my vault that I probably want to see all at once.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;anyway&quot;&gt;Anyway&lt;&#x2F;h2&gt;
&lt;p&gt;That&#x27;s how I organize my Obsidian vault. I have a few more plugins I use (e.g. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;liamcain&#x2F;obsidian-calendar-plugin&quot;&gt;a calendar widget&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;liamcain&#x2F;obsidian-periodic-notes&quot;&gt;improved daily notes&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SilentVoid13&#x2F;Templater&quot;&gt;templater&lt;&#x2F;a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;esm7&#x2F;obsidian-map-view&quot;&gt;a map view&lt;&#x2F;a&gt; for when I was looking at stuff to do on vacation) but that&#x27;s the core of it. I hope you can take some of this and get use out of it for yourself!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Scope in the TH element</title>
		<published>2022-12-19T00:00:00+00:00</published>
		<updated>2022-12-19T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/scope-in-the-th-element/" type="text/html"/>
		<id>https://bytes.zone/posts/scope-in-the-th-element/</id>
		<content type="html">&lt;p&gt;The &lt;code&gt;&amp;lt;th&amp;gt;&lt;&#x2F;code&gt; (table head) element in HTML accepts a &lt;code&gt;scope&lt;&#x2F;code&gt; attribute. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTML&#x2F;Element&#x2F;th#attr-scope&quot;&gt;According to MDN&lt;&#x2F;a&gt;, it can be one of these:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;row&lt;&#x2F;code&gt; and &lt;code&gt;col&lt;&#x2F;code&gt;, which means that the header relates to all the cells of the group (row or column, respectively) in which it&#x27;s found&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rowgroup&lt;&#x2F;code&gt; and &lt;code&gt;colgroup&lt;&#x2F;code&gt;, which specifies a group of rows or columns. I&#x27;m not sure how to make a group like this, though!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;th scope=&quot;row&quot;&amp;gt;&lt;&#x2F;code&gt; can be really useful for accessibility. Say you have a table like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: left&quot;&gt;Name&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Text-to-speech enabled&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Edit student&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Valencia Flowers&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;yes&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Romeo Harrison&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;no&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;If the &quot;name&quot; cells are &lt;code&gt;&amp;lt;th scope=&quot;row&quot;&amp;gt;&lt;&#x2F;code&gt;, screen readers can contextualize &quot;yes&quot; or &quot;no&quot; and the edit button when navigating between rows in a non-name column. For example, one might say something like &quot;text-to-speech enabled, Valencia Flowers, row, yes.&quot;&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Projects</title>
		<published>2022-12-14T00:00:00+00:00</published>
		<updated>2022-12-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/projects/" type="text/html"/>
		<id>https://bytes.zone/posts/projects/</id>
		<content type="html">&lt;p&gt;Just a little note: inspired by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sive.rs&#x2F;nowff&quot;&gt;&#x2F;now pages&lt;&#x2F;a&gt;, I&#x27;ve put a bunch of current and past projects up on the home page at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bytes.zone&quot;&gt;bytes.zone&lt;&#x2F;a&gt;, listed by date at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;&quot;&gt;bytes.zone&#x2F;projects&lt;&#x2F;a&gt;.
I&#x27;ll update them occasionally, adding blog posts for RSS readers as relevant.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re interested in what I&#x27;ve been up to lately, there it is!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>bold</title>
		<published>2022-12-14T00:00:00+00:00</published>
		<updated>2023-01-15T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/bold/" type="text/html"/>
		<id>https://bytes.zone/projects/bold/</id>
		<content type="html">&lt;p&gt;An attempt to get some of the things that interest me about large-scale monorepo build systems like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bazel.build&#x2F;&quot;&gt;Bazel&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buck.build&#x2F;&quot;&gt;Buck&lt;&#x2F;a&gt;, but without the learning curve or large footprint.
The main benefits I want are that Bold will run builds remotely by default, and all builds contribute to the cache.
It will be able to build locally too, of course, by running a local build node (local execution takes care of this for you, though)&lt;&#x2F;p&gt;
&lt;p&gt;This forces some decisions and tradeoffs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;tradeoff:&lt;&#x2F;strong&gt; you have to trust all builders to produce valid outputs, and all clients to not submit malicious jobs
&lt;ul&gt;
&lt;li&gt;this is not super different from normal open-source CI situations, though, and in private code you already have to have a trust layer (e.g. a hiring process.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;tradeoff:&lt;&#x2F;strong&gt; this makes incremental compilation harder. Where does the compilation cache live?
&lt;ul&gt;
&lt;li&gt;this might only seem bad and be fine in practice. We&#x27;ll have to see.&lt;&#x2F;li&gt;
&lt;li&gt;workaround: in some cases, you may be able to pre-build and cache dependencies. If life gets especially hard, you could separate a large program into several compilation units and cache those (e.g. packages in whatever language you&#x27;re using.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;implication:&lt;&#x2F;strong&gt; if builds are remote by default, we need to share and cache files efficiently. A content-addressable store is reasonably easy to understand and implement, so we&#x27;ve got one of those.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are more of these yet to come, I&#x27;m sure!&lt;&#x2F;p&gt;
&lt;p&gt;Bold was an interesting spike, but my priorities changed in early 2023 due to all the layoffs in the tech industry, so it didn&#x27;t go much further than this.
That said, I&#x27;m interested in the ideas and I&#x27;d love to come back to it some day.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Alloy</title>
		<published>2022-12-12T00:00:00+00:00</published>
		<updated>2022-12-12T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/alloy/" type="text/html"/>
		<id>https://bytes.zone/posts/alloy/</id>
		<content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;alloytools.org&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; is a tool for modeling software. It&#x27;s best at representing things like databases or data structures, but in my experience it does fine with things like UI states or processes as well.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve found Alloy most helpful in exploring the relationship between different parts of a system. Unlike other tools in the formal methods space, Alloy has a visualizer. For me, this is a huge advantage: not only does it make it easier for me to think about my models, but it also makes communicating about specs with people who are not familiar with Alloy much nicer. Having something you can talk about or mark up enables better communication, as opposed to having to read states and relationships out of a table.&lt;&#x2F;p&gt;
&lt;p&gt;To illustrate that, here&#x27;s an oversimplified version of email. I&#x27;ve told Alloy that there are many accounts on a server, and that accounts can send emails back and forth. This should even work across servers, as long as the servers can deliver to each other. The code looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Email&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Account&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Account&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Account&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  server&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: one&lt;&#x2F;span&gt;&lt;span&gt; Server&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;sig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Server&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  deliversTo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: set&lt;&#x2F;span&gt;&lt;span&gt; Server&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To start off, Alloy can generate a &quot;metamodel&quot; (that is, the relationships between things it knows about.) It basically looks like you might expect given the code:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;email-metamodel.png&quot; alt=&quot;a metamodel visualization generated by Alloy, with Email pointing &amp;quot;to&amp;quot; and &amp;quot;from&amp;quot; arrows to Account, Account pointing a &amp;quot;server&amp;quot; error to Server, and Server pointing &amp;quot;deliversTo&amp;quot; arrow to itself.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;With no code changes, it can also generate as many instances of the model as you care to ask it for. Here&#x27;s one, where an email is being sent from one server to another:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deliverable-email.png&quot; alt=&quot;an example generated by Alloy, with an email being delivered from an account on one server to an account on another&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve found it useful to set up relationships like this, then ask the tool to generate examples. If something looks off or out of the ordinary, I can go back and have a think (maybe with other people) about whether or not that thing should be allowed. For example, I notice that one email server above can&#x27;t see the other, and the other can&#x27;t deliver to its own accounts. Is that reasonable? Depending on the situation you&#x27;re modeling, it may or may not be!&lt;&#x2F;p&gt;
&lt;p&gt;You can use Alloy to make assertions about your models as well. In the example above, for example, you could assert that there couldn&#x27;t be any undeliverable email (that is, any email going between servers without a &lt;code&gt;deliversTo&lt;&#x2F;code&gt; relationship.) If you&#x27;ve allowed that possibility somehow, Alloy will tell you why and generate a counterexample. Here&#x27;s how we&#x27;d do that:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; NoUndeliverableEmail&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Email &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;server &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;server&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.*&lt;&#x2F;span&gt;&lt;span&gt;deliversTo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In English, this means &quot;for an email to be deliverable, the server in &lt;code&gt;from&lt;&#x2F;code&gt; must be able to reach the server in &lt;code&gt;to&lt;&#x2F;code&gt; through the &lt;code&gt;deliversTo&lt;&#x2F;code&gt; relation, 0 or more times. And, by the way, every Email is deliverable.&quot; Of course, we haven&#x27;t guaranteed that assertion in any way, so Alloy comes up with a counterexample:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;undeliverable-email.png&quot; alt=&quot;an example generated by Alloy, with an account trying to send email to a server that its server cannot deliver to&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this, &lt;code&gt;Server1&lt;&#x2F;code&gt; can deliver everywhere, &lt;code&gt;Server0&lt;&#x2F;code&gt; can deliver to &lt;code&gt;Server2&lt;&#x2F;code&gt;, and &lt;code&gt;Server2&lt;&#x2F;code&gt; can&#x27;t deliver anywhere. Unfortunately for everyone involved, &lt;code&gt;Account0&lt;&#x2F;code&gt; on &lt;code&gt;Server2&lt;&#x2F;code&gt; wants to send an email to their buddy on &lt;code&gt;Server1&lt;&#x2F;code&gt;. Guess you&#x27;re outta luck, pal!&lt;&#x2F;p&gt;
&lt;p&gt;We can try to fix this, of course, by saying that all of these &lt;code&gt;deliversTo&lt;&#x2F;code&gt; relations are symmetric. We&#x27;d just establish some fact that Alloy will take as given:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;alloy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fact&lt;&#x2F;span&gt;&lt;span&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt; delivery relationships are symmetric&amp;quot; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  deliversTo &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;= ~&lt;&#x2F;span&gt;&lt;span&gt;deliversTo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This works because &lt;code&gt;deliversTo&lt;&#x2F;code&gt; is actually a set of tuples; the &lt;code&gt;server.deliversTo&lt;&#x2F;code&gt; is doing a lookup into it! The &lt;code&gt;~&lt;&#x2F;code&gt; operator reverses all the tuples, so we&#x27;re asserting that the forward version is equal to the backwards one—symmetric!&lt;&#x2F;p&gt;
&lt;p&gt;But in the face of this stunning victory, Alloy replies &quot;ah, yes, but what if the servers are on completely separate networks?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;separate-networks.png&quot; alt=&quot;an example generated by Alloy showing two completely independent networks of email servers with one account trying to send an email across them. There are no connections between the networks, so this email is undeliverable.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We can keep going down this rabbit hole of generating examples and making assertions until we&#x27;re satisfied that the system has few enough problems to implement!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-is-this-useful&quot;&gt;Why is this Useful?&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve used Alloy in a lot of situations now where I would previously have just been guessing at edge cases in the model. It&#x27;s been really useful to show me exactly what problems exist in my model and helping me communicate with other people about why we should care about them.&lt;&#x2F;p&gt;
&lt;p&gt;Alloy helps simplify models, as well! For example, at work we were building a feature with inline commenting (like Google Docs.) We started by modeling Google Docs&#x27; comments and it turned out that each comment had something like 32 unique states. We realized this was going to be way too much for our use case, and simplified it down to 4. We probably could have had this realization without using Alloy, but it made the conversation much simpler—the designer and I sat down and looked at the visualizations, then kept saying &quot;but for us, states X and Y would be the same…&quot; until we arrived at a simpler model. It was really nice, and only took about an hour and a half of modeling to come up with a solution that satisfied all our requirements!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yeah-but&quot;&gt;Yeah, but…&lt;&#x2F;h2&gt;
&lt;p&gt;So all that&#x27;s super exciting, and I love using Alloy, but it&#x27;d be silly to pretend that it&#x27;s perfect. Here&#x27;s some examples:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It&#x27;s very easy to give Alloy contradicting instructions that rule out some case you care about. Sometimes this even means that Alloy can&#x27;t come up with any examples at all! It can be really difficult to debug this when you&#x27;re getting started. (But if you change the solver to &quot;MiniSat with Unsat Core&quot; you can at least get a hint at what might be conflicting.)&lt;&#x2F;li&gt;
&lt;li&gt;Alloy only checks a finite size of the models you generate (4 of everything by default.) This means that it won&#x27;t find bugs that require there to be a huge number of something. However, almost everything I&#x27;ve done in Alloy gives useful feedback with the default limits—it checks all states comprehensively within the given size—and it&#x27;s easy (bordering on trivial) to raise the limit if you suspect there would be a bug if you had more instances. Just know going in that you&#x27;re not going to get proof for an infinitely sized model. This property does mean that Alloy is very, very fast, though!&lt;&#x2F;li&gt;
&lt;li&gt;As a beginner, it&#x27;s hard to know how to learn. The usual learning path is to buy and work through &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;alloytools.org&#x2F;book.html&quot;&gt;&lt;em&gt;Software Abstractions&lt;&#x2F;em&gt; by Alloy&#x27;s author, Daniel Jackson&lt;&#x2F;a&gt;. However, Alloy has evolved over time and now has important features—like temporal logic—that are not covered in the book. To make matters worse, documentation outside of the normal learning path often means reading &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;alloytools.org&#x2F;spec.html&quot;&gt;the spec&lt;&#x2F;a&gt;. However, once you know a bit about Alloy, and even while you&#x27;re learning, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloy.readthedocs.io&#x2F;en&#x2F;latest&#x2F;&quot;&gt;Hillel Wayne&#x27;s Alloy reference&lt;&#x2F;a&gt; is great.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Another one of Alloy&#x27;s weakness &lt;em&gt;used to be&lt;&#x2F;em&gt; time—that is, it was hard to model things that could change—but the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.org&#x2F;alloy6.html&quot;&gt;release of Alloy 6&lt;&#x2F;a&gt; added temporal logic and made this situation a lot more tenable. I still haven&#x27;t completely come to grips with the new features, but I see concepts in the spec that I first learned about in TLA+—although I appreciate that Alloy uses English-language keywords instead of symbol operators like &lt;code&gt;[]&lt;&#x2F;code&gt; (&lt;code&gt;always&lt;&#x2F;code&gt; in Alloy) or &lt;code&gt;&amp;lt;&amp;gt;&lt;&#x2F;code&gt; (&lt;code&gt;eventually&lt;&#x2F;code&gt; in Alloy.)&lt;&#x2F;p&gt;
&lt;p&gt;All told, I recommend learning Alloy! I&#x27;ve found it tremendously helpful and I think it&#x27;s too bad that it&#x27;s not better-known by industry programmers.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>learning Alloy</title>
		<published>2022-08-01T00:00:00+00:00</published>
		<updated>2023-10-09T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/learning-alloy/" type="text/html"/>
		<id>https://bytes.zone/projects/learning-alloy/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been an on-and-off user of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alloytools.org&#x2F;&quot;&gt;Alloy&lt;&#x2F;a&gt; for several years now, but after switching teams at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;noredink.com&quot;&gt;NoRedInk&lt;&#x2F;a&gt; I decided to start modeling all our new projects in it and teaching my colleagues how to as well.
This has been really interesting work!
Modeling with Alloy has frequently meant finding out about edge cases in our thinking (or in some cases, finding out we were totally wrong.)&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>home recycling</title>
		<published>2022-07-01T00:00:00+00:00</published>
		<updated>2022-07-01T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/home-recycling/" type="text/html"/>
		<id>https://bytes.zone/projects/home-recycling/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been interested both in environmentalism and in making things for a long time, but hadn&#x27;t realized until the summer of 2021 that I could combine those into something interesting!
Inspired by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.brothersmake.com&#x2F;&quot;&gt;The Brothers Make&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;preciousplastic.com&#x2F;&quot;&gt;Precious Plastic&lt;&#x2F;a&gt;, I&#x27;ve started saving all the #2 plastic that we use at home—milk jugs, shampoo and detergent bottles, etc—and melting it down as a raw material to make new things.
I&#x27;m still beginning to figure this out (I have more plastic stored than melted) but I&#x27;m excited by the possibilities!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>being the wandering toolmaker</title>
		<published>2022-05-01T00:00:00+00:00</published>
		<updated>2023-10-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/being-the-wandering-toolmaker/" type="text/html"/>
		<id>https://bytes.zone/projects/being-the-wandering-toolmaker/</id>
		<content type="html">&lt;p&gt;After being an senior-ish software engineer and a team lead (like a tech lead elsewhere) for five years, I was the first &quot;wandering toolmaker&quot; at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;noredink.com&quot;&gt;NoRedInk&lt;&#x2F;a&gt;.
In this role, I spent about half of my time working on the product (mostly focused on improving life for &lt;abbr title=&quot;English Language Arts&quot;&gt;ELA&lt;&#x2F;abbr&gt; teachers and their students) and the remainder working on improving tooling and developer experience for the department and company.&lt;&#x2F;p&gt;
&lt;p&gt;This included projects like:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;adding &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sorbet.org&#x2F;&quot;&gt;Sorbet&lt;&#x2F;a&gt; to our Rails monolith and getting almost all (over 95%) of our code to &lt;code&gt;typed: true&lt;&#x2F;code&gt; levels of checking.&lt;&#x2F;li&gt;
&lt;li&gt;converting all our CoffeeScript code to JavaScript, then to TypeScript, and integrating it with the port-based FFI in Elm.&lt;&#x2F;li&gt;
&lt;li&gt;creating tools to help our accessibility team educate the company how to make our site more accessible&lt;&#x2F;li&gt;
&lt;li&gt;exploring alternate build tools like &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;rbt&#x2F;&quot;&gt;rbt&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>tree-grepper</title>
		<published>2021-08-31T00:00:00+00:00</published>
		<updated>2021-08-31T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/tree-grepper/" type="text/html"/>
		<id>https://bytes.zone/posts/tree-grepper/</id>
		<content type="html">&lt;p&gt;A while ago I wanted to build an import graph from all the frontend code at NoRedInk to build some developer tools.
The code I wrote ended up working fine, but it was also pretty messy… tons of big regular expressions to make sure I got all the corner cases and whitespace allowed in the syntax.
I thought there probably was a better way, especially since I had just learned about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tree-sitter.github.io&#x2F;tree-sitter&#x2F;&quot;&gt;tree-sitter&lt;&#x2F;a&gt;.
I also wanted to learn some Rust, so… well, it sounded like a fun little project!&lt;&#x2F;p&gt;
&lt;p&gt;Fast forward about a year and I just released &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;tree-grepper&quot;&gt;&lt;code&gt;tree-grepper&lt;&#x2F;code&gt; 2.0.0&lt;&#x2F;a&gt;!
It lets you search very quickly across large projects full of diverse filetypes, using tree-sitter grammars and search queries.&lt;&#x2F;p&gt;
&lt;p&gt;The big benefit here is that it&#x27;s easy to expand: tree-sitter is getting really popular, what with language servers and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;neovim.io&#x2F;doc&#x2F;treesitter&#x2F;&quot;&gt;Neovim extensions&lt;&#x2F;a&gt; and everything, so it&#x27;s pretty likely that someone has already built a parser that we can just add!
Currently tree-grepper lets you search Elm, Haskell, JavaScript, Ruby, Rust, and TypeScript, but it&#x27;s pretty easy to add more, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;tree-grepper#supported-languages&quot;&gt;the README has a step-by-step guide&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Tree-grepper is focused only on search: it doesn&#x27;t do linting (like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;semgrep.dev&quot;&gt;semgrep&lt;&#x2F;a&gt;) or AST-based refactoring (like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;comby.dev&#x2F;&quot;&gt;comby&lt;&#x2F;a&gt;.)
You can get structured match data out of it, but any further processing is another tool&#x27;s responsibility.
This let me cut down on scope significantly, and optimize for searching alone.
Hooray for the Unix philosophy!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;extracting-an-import-graph&quot;&gt;Extracting An Import Graph&lt;&#x2F;h2&gt;
&lt;p&gt;Let me show off what it can do a little bit by implementing the parsing task I set out to do originally!&lt;&#x2F;p&gt;
&lt;p&gt;Tree-sitter implements an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tree-sitter.github.io&#x2F;tree-sitter&#x2F;using-parsers#pattern-matching-with-queries&quot;&gt;s-expression query API&lt;&#x2F;a&gt; which tree-grepper exposes directly.
We&#x27;ll use that to query for all the imports in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;noredink&#x2F;noredink-ui&quot;&gt;NoRedInk&#x2F;noredink-ui&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; elm &amp;#39;(import_clause)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Category.elm:18:1:query:import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Sort exposing&lt;&#x2F;span&gt;&lt;span&gt; (Sorter)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:3:1:query:import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Accessibility.Styled as Html exposing&lt;&#x2F;span&gt;&lt;span&gt; (Html,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; img, text&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:4:1:query:import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Browser exposing&lt;&#x2F;span&gt;&lt;span&gt; (Document,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; UrlRequest&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:5:1:query:import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Browser.Dom&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:6:1:query:import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Browser.Navigation exposing&lt;&#x2F;span&gt;&lt;span&gt; (Key)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What we&#x27;re doing here is asking &lt;code&gt;tree-grepper&lt;&#x2F;code&gt; to give us all the &lt;code&gt;import_clause&lt;&#x2F;code&gt; nodes it finds in &lt;code&gt;elm&lt;&#x2F;code&gt; files (I&#x27;ll tell you how to find out how this is called an &lt;code&gt;import_clause&lt;&#x2F;code&gt; later.)
Each match gets printed as a line of text along with the exact position in the source file.&lt;&#x2F;p&gt;
&lt;p&gt;We don&#x27;t want the entire match, though, just the module name (the part after &lt;code&gt;import&lt;&#x2F;code&gt; but before &lt;code&gt;as&lt;&#x2F;code&gt; or &lt;code&gt;exposing&lt;&#x2F;code&gt;).
Let&#x27;s try and get just the module name:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; elm &amp;#39;(import_clause (upper_case_qid)@import)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Category.elm:18:7:import:Sort&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:3:8:import:Accessibility.Styled&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:4:8:import:Browser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:5:8:import:Browser.Dom&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:6:8:import:Browser.Navigation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So now we&#x27;re asking for &lt;code&gt;upper_case_qid&lt;&#x2F;code&gt; nodes inside &lt;code&gt;import_clause&lt;&#x2F;code&gt;s.
If we tag the nodes we care about (by naming them after an &lt;code&gt;@&lt;&#x2F;code&gt;), &lt;code&gt;tree-grepper&lt;&#x2F;code&gt; will only output the parts we tagged.&lt;&#x2F;p&gt;
&lt;p&gt;So far, so good!
But how about &lt;code&gt;module Foo exposing (..)&lt;&#x2F;code&gt; at the tops of files, too?
Easy: just add another &lt;code&gt;--query&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; elm &amp;#39;(module_declaration (upper_case_qid)@module)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; elm &amp;#39;(import_clause (upper_case_qid)@import)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Category.elm:1:8:module:Category&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Category.elm:19:8:import:Sort&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:1:8:module:Main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:3:8:import:Accessibility.Styled&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:4:8:import:Browser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This follows the same pattern: we want a named child of another named node in the file, and &lt;code&gt;tree-grepper&lt;&#x2F;code&gt; manages walking the tree to give it to us.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;javascript-imports&quot;&gt;JavaScript Imports&lt;&#x2F;h2&gt;
&lt;p&gt;What about JavaScript &lt;code&gt;import&lt;&#x2F;code&gt; clauses?
We can mix different languages as easily as we mix different queries, but let&#x27;s do one at a time for simplicity&#x27;s sake:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; javascript &amp;#39;(import_statement (string (string_fragment)@import) .)&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;lib&#x2F;TextArea&#x2F;V4.js:1:31:import:..&#x2F;CustomElement&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the end of a &lt;code&gt;import * as Foo from &quot;Bar&quot;&lt;&#x2F;code&gt; clause.
The &lt;code&gt;.&lt;&#x2F;code&gt; at the end is an anchor: it tells tree-sitter that we care that the thing we&#x27;re matching is the last child of its parent node.
You can also put a &lt;code&gt;.&lt;&#x2F;code&gt; right after the node name to match on the first node only, or on both sides to enforce that you are matching the only node.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;re also matching a string fragment out of the string we require.
This lets us remove any quoting characters so we get &lt;code&gt;..&#x2F;CustomElement&lt;&#x2F;code&gt; out instead of &lt;code&gt;&#x27;..&#x2F;CustomElement&#x27;&lt;&#x2F;code&gt;.
We could also put anchors here to make sure we&#x27;re not getting any interpolated strings, but I haven&#x27;t needed to do that yet so we&#x27;re not here either.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;javascript-requires&quot;&gt;JavaScript Requires&lt;&#x2F;h2&gt;
&lt;p&gt;Finally, let&#x27;s get &lt;code&gt;require&lt;&#x2F;code&gt; calls:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; javascript &amp;#39;(call_expression (identifier)@_fn (arguments . (string (string_fragment)@require) .) (#eq? @_fn require))&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;manifest.js:1:8:require:..&#x2F;lib&#x2F;index.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;script&#x2F;percy-tests.js:1:29:require:@percy&#x2F;script&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;script&#x2F;axe-puppeteer.js:4:27:require:puppeteer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;script&#x2F;axe-puppeteer.js:5:25:require:axe-core&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;script&#x2F;axe-puppeteer.js:6:37:require:url&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is quite the query!
Let&#x27;s break it up over multiple lines to talk about it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(call_expression&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (identifier)@_fn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (arguments &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt; (string_fragment)@require) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (#eq? @_fn &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;(call_expression (identifier) (arguments))&lt;&#x2F;code&gt; is a function or method call, in our case &lt;code&gt;require(&quot;url&quot;)&lt;&#x2F;code&gt;.
However, without anchors or specifying the arguments we&#x27;re not saying anything about the contents, just that it&#x27;s a call.
In this case, we care that the arguments are only a single string, so we specify that as &lt;code&gt;(arguments . (string (string_fragment)@require) .)&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, we don&#x27;t want &lt;em&gt;just any&lt;&#x2F;em&gt; function with a single string argument; we only want &lt;code&gt;require&lt;&#x2F;code&gt; statements.
Tree-sitter exposes a couple of matcher functions (&lt;code&gt;#eq?&lt;&#x2F;code&gt; for string equality and &lt;code&gt;#match?&lt;&#x2F;code&gt; for regular expressions) to select the things we want here.
To use them, we name the node we care about (here &lt;code&gt;@_fn&lt;&#x2F;code&gt; with a leading underscore to tell tree-grepper to drop it from the output,) then give the match to &lt;code&gt;#eq?&lt;&#x2F;code&gt; along with a bare word (&lt;code&gt;require&lt;&#x2F;code&gt;) to check for equality.
Now we only match nodes that look like &lt;code&gt;require(&#x27;axe-core&#x27;)&lt;&#x2F;code&gt;, but only pull out the inner &lt;code&gt;axe-core&lt;&#x2F;code&gt; string that we care about!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting It All Together&lt;&#x2F;h2&gt;
&lt;p&gt;Of course, we can do this all at once, smashing all our queries together in one giant &lt;code&gt;tree-grepper&lt;&#x2F;code&gt; invocation.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; elm &amp;#39;(import_clause (upper_case_qid)@import)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; elm &amp;#39;(module_declaration (upper_case_qid)@module)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; javascript &amp;#39;(import_statement (string (string_fragment)@import) .)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; javascript &amp;#39;(call_expression (identifier)@_fn (arguments . (string (string_fragment)@require) .) (#eq? @_fn require))&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Category.elm:1:8:module:Category&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Category.elm:19:8:import:Sort&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:1:8:module:Main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:3:8:import:Accessibility.Styled&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;.&#x2F;styleguide-app&#x2F;Main.elm:4:8:import:Browser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That works for any amount of queries you&#x27;d care to throw at it!
In fact, it&#x27;s more efficient to run this way since it only has to walk the filesystem and parse files once!&lt;&#x2F;p&gt;
&lt;p&gt;You can also get more information by specifying the format: the &lt;code&gt;json&lt;&#x2F;code&gt; and &lt;code&gt;pretty-json&lt;&#x2F;code&gt; formats have match end locations as well as starts, and they include the node names returned.
You can use that to get an overview of all the node names in a grammar:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; echo &amp;#39;console.log(&amp;quot;Hello, World!&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; hello.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tree-grepper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --query&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; javascript &amp;#39;(_)&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --format&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; pretty-json hello.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;quot;file&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;hello.js&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;quot;file_type&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;javascript&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;quot;matches&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;kind&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;program&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;query&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;text&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;console.log(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;Hello, World!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;)\n&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;start&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;          &amp;quot;row&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: 1,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;          &amp;quot;column&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;end&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;          &amp;quot;row&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: 2,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;          &amp;quot;column&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ... snip ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;ve actually had to shorten that significantly because it&#x27;s so long!
If you&#x27;d like to see some full output, check the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;tree-grepper&#x2F;tree&#x2F;main&#x2F;src&#x2F;snapshots&quot;&gt;&lt;code&gt;all_{language}.snap&lt;&#x2F;code&gt; snapshot test files in the tree-grepper repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I really enjoyed writing this tool and learning more about tree-sitter, and I hope you find it useful!
You can also get the source and instructions for contributing at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;tree-grepper&quot;&gt;github.com&#x2F;BrianHicks&#x2F;tree-grepper&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Tree-grepper is packaged using Nix, so if you have that you can just install it like &lt;code&gt;nix-env -if https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;tree-grepper&#x2F;archive&#x2F;refs&#x2F;heads&#x2F;main.tar.gz&lt;&#x2F;code&gt;.
If you have Nix flakes enabled, you can also run &lt;code&gt;nix shell github:BrianHicks&#x2F;tree-grepper&lt;&#x2F;code&gt; to get a shell with &lt;code&gt;tree-grepper&lt;&#x2F;code&gt; already available.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>The Four Rules of Simple Design: In Conclusion</title>
		<published>2021-08-25T00:00:00+00:00</published>
		<updated>2021-08-25T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/the-four-rules-of-simple-design-conclusion/" type="text/html"/>
		<id>https://bytes.zone/posts/the-four-rules-of-simple-design-conclusion/</id>
		<content type="html">&lt;p&gt;OK, it&#x27;s been a couple of weeks since I published &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-4-code-for-now&#x2F;&quot;&gt;Rule 4: Code for Now&lt;&#x2F;a&gt;, and I&#x27;ve had some interesting feedback!&lt;&#x2F;p&gt;
&lt;p&gt;The biggest thing first: how do you apply these?
Surely you don&#x27;t just go down the list one at a time, right?
Well, no, I view it more like like a state machine: you enter at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-1-simplify-when-the-program-works&#x2F;&quot;&gt;Rule 1: Simplify When the Program Works&lt;&#x2F;a&gt; and move from there back and forth between &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-2-clarify-your-intent&#x2F;&quot;&gt;Rule 2: Clarify Your Intent&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-3-centralize-behavior&#x2F;&quot;&gt;Rule 3: Centralize Behavior&lt;&#x2F;a&gt;.
Once you think you&#x27;re more-or-less done, you check your work with &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-4-code-for-now&#x2F;&quot;&gt;Rule 4: Code for Now&lt;&#x2F;a&gt;.
When you don&#x27;t have anywhere else to go—that is, you don&#x27;t see an opportunity to apply any of the rules—you call it good and move on to the next thing you need to do that day!&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s not the only way to apply the rules, of course, but it&#x27;s one that I&#x27;ve found works pretty well for me.
J.B. Rainsberger applies the original rules something like this as well: see his post &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.jbrains.ca&#x2F;permalink&#x2F;the-four-elements-of-simple-design&quot;&gt;The Four Elements of Simple Design&lt;&#x2F;a&gt; for more, in particular his &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.jbrains.ca&#x2F;permalink&#x2F;the-four-elements-of-simple-design#the-two-elements-of-simple-design&quot;&gt;axioms of modular design&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, it&#x27;s not the only way by a long shot! One of my &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;www.mike.is&#x2F;&quot;&gt;colleagues at NoRedInk&lt;&#x2F;a&gt; pointed out that he&#x27;d like to rearrange these rules:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Code for now&lt;&#x2F;li&gt;
&lt;li&gt;Simplify when the program works&lt;&#x2F;li&gt;
&lt;li&gt;Clarify intent&lt;&#x2F;li&gt;
&lt;li&gt;Centralize behavior&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That could certainly work too! It&#x27;s up to you how you apply these, if you want to apply them at all.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s also some tension between the rules. For example, in &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-3-centralize-behavior&#x2F;&quot;&gt;Rule 3: Centralize Behavior&lt;&#x2F;a&gt; I talked a lot about making a &lt;code&gt;Point2d&lt;&#x2F;code&gt; class.
But… is that structure really &lt;em&gt;necessary&lt;&#x2F;em&gt;?
Couldn&#x27;t it violate &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-4-code-for-now&#x2F;&quot;&gt;Rule 4: Code for Now&lt;&#x2F;a&gt;?
How do you decide which to apply?
In the end, you&#x27;ve either got to choose randomly or find a tiebreaker: for example, you could use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;thoughtbot.com&#x2F;blog&#x2F;sandi-metz-rules-for-developers&quot;&gt;Sandi Metz&#x27; Rules For Developers&lt;&#x2F;a&gt;, which emphasize very small classes.
In that context, building a &lt;code&gt;Point2d&lt;&#x2F;code&gt; may or may not be OK depending on how close your class is to 100 lines.&lt;&#x2F;p&gt;
&lt;p&gt;Personally, I&#x27;ve usually gone the other way over the time: I&#x27;ve randomly chosen to prioritize rules randomly based on the situation at hand.
However, since part of the appeal of this whole exercise is that they allow us to reduce guessing about the future, I&#x27;m going to continue to explore situations where there seem to be holes or conflicts!&lt;&#x2F;p&gt;
&lt;p&gt;And, to that end: if you read through the original sources (or mine) and are inspired to make your own remix of the four rules, I&#x27;d love to read it!
Please send me your takes!
Let&#x27;s build on each other&#x27;s work to discover how to make better software.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks for reading!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Rule 4: Code for Now</title>
		<published>2021-07-13T00:00:00+00:00</published>
		<updated>2021-07-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/rule-4-code-for-now/" type="text/html"/>
		<id>https://bytes.zone/posts/rule-4-code-for-now/</id>
		<content type="html">&lt;p&gt;Last week, we talked about &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-3-centralize-behavior&#x2F;&quot;&gt;centralizing behavior&lt;&#x2F;a&gt; as part of the &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;my-take-on-the-four-rules-of-simple-design&#x2F;&quot;&gt;four rules of simple design series&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This week, the final rule: &lt;strong&gt;code for now&lt;&#x2F;strong&gt;.
This is about focusing on building the smallest thing that can possibly work.&lt;&#x2F;p&gt;
&lt;p&gt;Another way to put it: are you writing code because you&#x27;re guessing that you&#x27;ll need something later?
Stop it!&lt;&#x2F;p&gt;
&lt;p&gt;This shows up most commonly when building larger pieces of program structure.
In my experience:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In object-oriented languages like Python and Ruby, it can look like deep class hierarchies and composition by mixins or inheritance.&lt;&#x2F;li&gt;
&lt;li&gt;In Haskell, it can look like lots of custom typeclasses or adding high-complexity features like lenses or monad transformer stacks.&lt;&#x2F;li&gt;
&lt;li&gt;In Elm, it can look like factoring out every little piece of state into a separate model&#x2F;update&#x2F;view triple, or splitting things into smaller and smaller modules.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In summary: don&#x27;t add structure to code before you need it.
(And if we&#x27;re being honest, this rule is not far from &quot;You Aren&#x27;t Gonna Need It&quot; in different clothes.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-happens-if-you-ignore-this&quot;&gt;What Happens If You Ignore This?&lt;&#x2F;h2&gt;
&lt;p&gt;Have you ever seen &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;EnterpriseQualityCoding&#x2F;FizzBuzzEnterpriseEdition&quot;&gt;FizzBuzzEnterpriseEdition&lt;&#x2F;a&gt;?
That.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;but-where-do-i-put-code&quot;&gt;But Where Do I Put Code?&lt;&#x2F;h2&gt;
&lt;p&gt;Removing unnecessary structure can cause some confusion when you want to add some functionality but there&#x27;s no obvious place for it.
In that case: add structure, because you need it &lt;em&gt;now&lt;&#x2F;em&gt;.
The point of the rule is to avoid guessing about what you&#x27;ll need in the future, not to avoid all kinds of structure.
This rule is also the catch-all at the end of the four rules.
You shouldn&#x27;t need to invoke it often.
If you don&#x27;t know where to put code, make sure you&#x27;re &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-3-centralize-behavior&#x2F;&quot;&gt;centralizing behavior&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-2-clarify-your-intent&#x2F;&quot;&gt;clarifying your intent&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Rule 3: Centralize Behavior</title>
		<published>2021-07-06T00:00:00+00:00</published>
		<updated>2021-07-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/rule-3-centralize-behavior/" type="text/html"/>
		<id>https://bytes.zone/posts/rule-3-centralize-behavior/</id>
		<content type="html">&lt;p&gt;Last week, we talked about &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-2-clarify-your-intent&#x2F;&quot;&gt;clarifying intent&lt;&#x2F;a&gt; as part of the &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;my-take-on-the-four-rules-of-simple-design&#x2F;&quot;&gt;four rules of simple design series&lt;&#x2F;a&gt;.
This week, we&#x27;re on to rule 3: &lt;strong&gt;centralize behavior.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Imagine you&#x27;re working on project for a school, trying to make sure that the site addresses users appropriately: students should be addressed casually (&quot;Hello, Adrian!&quot;) while teachers should be addressed more formally (&quot;Your teacher, Dr. Beans&quot;.) Imagine that this has been done, but a little bit at a time and in different ways, so that these forms of address are implemented in different and inconsistent ways across the site. That makes what seems like a simple change much more difficult!&lt;&#x2F;p&gt;
&lt;p&gt;In order to make this change tractable, you might create a function or method that takes a &lt;code&gt;User&lt;&#x2F;code&gt; or &lt;code&gt;Role&lt;&#x2F;code&gt; domain concept and returns the right string form, then change each call site across the whole project to use that. A big project, sure, but a pretty reasonable approach! This demonstrates our third rule: &lt;strong&gt;centralize behavior&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is similar in spirit to the ideas behind Don&#x27;t Repeat Yourself. In that context, &quot;repetition&quot; isn&#x27;t talking about the written form of code so much as authority over defining the behaviors and ideas in the system. A similar concept comes up in Once and Only Once: define each concept in your system in exactly one place, and don&#x27;t split up that authority!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-happens-if-you-ignore-this&quot;&gt;What Happens If You Ignore This&lt;&#x2F;h2&gt;
&lt;p&gt;Like in the scenario above, ignoring this rule results in hard-to-change code which needs lots of extra effort to modify successfully. Did you get all the little pieces? Better hope so!&lt;&#x2F;p&gt;
&lt;p&gt;Problem is, it&#x27;s often hard to see this happening—the name change I described above feels a bit contrived because it&#x27;s easy to see, but template systems in web frameworks will push you away from specifying one-off logic and towards reusable functions or methods. I&#x27;ve seen this come up more often in serialization and validation logic: places where it&#x27;s very tempting to just dig into some internal data structure instead of defining behavior where the code lives.&lt;&#x2F;p&gt;
&lt;p&gt;One question to ask is &quot;who should be responsible for this behavior?&quot; In the long term, it&#x27;s nice to answer this by writing behavior attractors.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;behavior-attractors&quot;&gt;Behavior Attractors&lt;&#x2F;h2&gt;
&lt;p&gt;Behavior attractors (a term coined by Corey Haines in his book &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;leanpub.com&#x2F;4rulesofsimpledesign&quot;&gt;Understanding the 4 Rules of Simple Design&lt;&#x2F;a&gt;) are places in your code where it becomes natural to put related code, like water flowing downhill. This gives you a natural way to centralize behavior to keep your system simple and easy to understand.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s look at an example! &lt;em&gt;Understanding the 4 rules of Simple Design&lt;&#x2F;em&gt; uses Conway&#x27;s Game of Life throughout, so let&#x27;s just steal the example Haines uses to explain this: how do we figure out the neighbors of a cell? When you&#x27;re implementing the Game of Life, you might end up writing &lt;code&gt;Cell.neighbors&lt;&#x2F;code&gt; in the course of answering &quot;is this cell alive or dead in the next cycle?&quot; However, this is not necessarily the best way to implement this, because the concept of a neighbor really depends on the topology of the grid. &quot;Neighbor&quot; means something different for 2- versus 3-dimensional grids, not to mention things like hyperbolic geometry. That suggests that &quot;are these cells neighbors&quot; should be defined by the coordinate system, but you might not come to this realization if you&#x27;re just passing around 2-dimensional coordinates like &lt;code&gt;(x, y)&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The behavior attractor solution is to move those coordinates to a central definition (say, &lt;code&gt;2DCoord&lt;&#x2F;code&gt;) and implement the &lt;code&gt;neighbors&lt;&#x2F;code&gt; function&#x2F;method there instead. That means you might end up with &lt;code&gt;2DCoord(1, 1).neighbors&lt;&#x2F;code&gt; instead of &lt;code&gt;Cell.at(1, 1).neighbors&lt;&#x2F;code&gt;. You get a clearer separation of concerns: a &lt;code&gt;2DCoord&lt;&#x2F;code&gt; can clearly define neighbors in a way that a &lt;code&gt;Cell&lt;&#x2F;code&gt; shouldn&#x27;t be responsible for. The behavior attractor made this possible, and will make your life easier the next time you need to ask a question about the coordinate system too!&lt;&#x2F;p&gt;
&lt;p&gt;If you find that you don&#x27;t have a good place to put some code, &lt;em&gt;especially&lt;&#x2F;em&gt; if you find yourself just putting it close to the place you need it, consider writing a behavior attractor!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Rule 2: Clarify Your Intent</title>
		<published>2021-06-29T00:00:00+00:00</published>
		<updated>2021-06-29T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/rule-2-clarify-your-intent/" type="text/html"/>
		<id>https://bytes.zone/posts/rule-2-clarify-your-intent/</id>
		<content type="html">&lt;p&gt;Last week, we talked about &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-1-simplify-when-the-program-works&#x2F;&quot;&gt;simplifying when the program works&lt;&#x2F;a&gt; as part of the &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;my-take-on-the-four-rules-of-simple-design&#x2F;&quot;&gt;four rules of simple design series&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This week, rule two: &lt;strong&gt;clarify your intent.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The original four rules prioritize clarity of communication&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#four-rules-are-about-communication&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. For example, Beck&#x27;s formulation says &quot;states every intention important to the programmer.&quot; Fowler tightens that up to &quot;reveals intent&quot;, and Rainsberger refines it further to &quot;improve names.&quot; But &lt;em&gt;what&lt;&#x2F;em&gt; intent are we trying to communicate clearly? Here are a few things you might want to consider:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;What&#x27;s this thing for?&lt;&#x2F;li&gt;
&lt;li&gt;How are these two things related?&lt;&#x2F;li&gt;
&lt;li&gt;Why is this code doing things &lt;em&gt;this way&lt;&#x2F;em&gt; instead of &lt;em&gt;that way&lt;&#x2F;em&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;Please don&#x27;t do &lt;em&gt;this thing&lt;&#x2F;em&gt; if you also do &lt;em&gt;that thing&lt;&#x2F;em&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Which part of this is core to the program, and which is just supporting structure?&lt;&#x2F;li&gt;
&lt;li&gt;Where does the stuff you send here end up? Or, where is this thing getting stuff from?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can communicate these things in a lot of ways, but I want to talk about three: names, comments, and constructors.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;names&quot;&gt;Names&lt;&#x2F;h2&gt;
&lt;p&gt;Say you have a method called &lt;code&gt;process_data&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#process-data-inspiration&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. Both of those words are really vague! What if we made them better? What&#x27;s &lt;code&gt;process&lt;&#x2F;code&gt;? What&#x27;s &lt;code&gt;data&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;Maybe you look at the method body and find out it downloads a CSV of accounts and selects all the ones the sales team has identified as likely to buy a premium license. So in this case, &lt;code&gt;process&lt;&#x2F;code&gt; would be incorrect—it&#x27;s &lt;code&gt;get&lt;&#x2F;code&gt; or &lt;code&gt;download&lt;&#x2F;code&gt;. And it&#x27;s not just any old &lt;code&gt;data&lt;&#x2F;code&gt;, it&#x27;s &lt;code&gt;clients&lt;&#x2F;code&gt;, specially ones on the threshold of upgrading.&lt;&#x2F;p&gt;
&lt;p&gt;So, while there are more ways you could improve this (e.g. separating the downloading from the data processing), renaming it to &lt;code&gt;get_threshold_clients&lt;&#x2F;code&gt; already makes it way easier to understand your code: imagine seeing &lt;code&gt;process_data&lt;&#x2F;code&gt; vs &lt;code&gt;get_threshold_clients&lt;&#x2F;code&gt; at a call site. Which would you prefer when reading later?&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a lot to think about when choosing a good name, so instead of saying more, I&#x27;m just going to link some things I&#x27;ve found helpful: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;2018.elm-conf.us&#x2F;schedule&#x2F;ally-kelly-mcknight&quot;&gt;&lt;em&gt;Naming Things in Elm&lt;&#x2F;em&gt; by Ally McKnight&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;macwright.com&#x2F;2021&#x2F;02&#x2F;17&#x2F;the-naming-of-things.html&quot;&gt;&lt;em&gt;Picking better names for variables, functions, and projects&lt;&#x2F;em&gt; by Tom MacWright&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.sitepoint.com&#x2F;whats-in-a-name-anti-patterns-to-a-hard-problem&#x2F;&quot;&gt;&lt;em&gt;What&#x27;s in a Name? Anti-Patterns to Hard Problem&lt;&#x2F;em&gt; by Katrina Owen&lt;&#x2F;a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buttondown.email&#x2F;hillelwayne&#x2F;archive&#x2F;naming-things-is-a-poor-name-for-naming-things&#x2F;&quot;&gt;&lt;em&gt;&quot;Naming Things&quot; is a Poor Name for Naming Things&lt;&#x2F;em&gt; by Hillel Wayne&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;&#x2F;h2&gt;
&lt;p&gt;Another thing you could do to clarify your intent is to explicitly write it down in a comment. There&#x27;s the constant debate on &quot;what&quot; comments vs &quot;why&quot; comments, of course—&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;buttondown.email&#x2F;hillelwayne&#x2F;archive&#x2F;comment-the-why-and-the-what&#x2F;&quot;&gt;here&#x27;s Hillel Wayne again on why you need both&lt;&#x2F;a&gt;—but in either form, the best comments give the reader insight into what was going on and what you needed when you wrote your code.&lt;&#x2F;p&gt;
&lt;p&gt;I like to write long documentation comments framed like &quot;Hello, future us! I hope you&#x27;re having great day. Here&#x27;s what&#x27;s up.&quot; I&#x27;ve found those to be helpful, both when my coworkers review the code and when we revisit it months later while trying to do something else. Having little hints of intention scattered throughout the code helps remember things we need to keep in mind. Plus, like trying to find a good name, documenting why something exists can often help us realize some other refactor that could help us do what we&#x27;re doing better!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;constructors&quot;&gt;Constructors&lt;&#x2F;h2&gt;
&lt;p&gt;In addition to names and comments, you can often clarify your intent in by making certain circumstances impossible by construction. As a trivial example, you might know that a value can sometimes be null, so you represent it as an optional type. Or, if you know you&#x27;ll always have at least one item, you can use a non-empty list to hold the data. I&#x27;ve found this to be a lot easier in ML-family languages like Haskell or Elm, but you can do it in object-oriented languages as well.&lt;&#x2F;p&gt;
&lt;p&gt;There are tons of talks and examples of this online if you search for things like &quot;make illegal states unrepresentable&quot;. I like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=IcgmSRJHu_8&quot;&gt;Richard Feldman&#x27;s 2016 talk &lt;em&gt;Make Impossible States Impossible&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-happens-if-you-ignore-this&quot;&gt;What Happens If You Ignore This?&lt;&#x2F;h2&gt;
&lt;p&gt;If you don&#x27;t clarify your intent when coding, you end up in situations where you have to re-establish context. In names, that might mean having to jump to the definition to figure out what&#x27;s actually happening. In the case of comments, that might mean having to spend a long time looking through the code in order to find the shape of the program&#x27;s core algorithm. In constructors, it means the same but trying to figure out if something should be possible or not.&lt;&#x2F;p&gt;
&lt;p&gt;If you don&#x27;t clarify intent, you&#x27;ll also miss out on great refactoring opportunities: in the &lt;code&gt;get_threshold_clients&lt;&#x2F;code&gt; example above, we might see that we want to separate getting the raw data and applying our precise filtering rules. That makes the code more modular (and more testable!) but we might not have seen it if we didn&#x27;t stop to clarify intent.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;In summary, remember that code is read many more times than it&#x27;s written. Keeping that in mind can go a long way to making things better for your coworkers (or just you, 6 months from now.) There are lot of ways to do this—way more than we can go over here—but you can get a long way by focusing on names, comments, and constructors.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;four-rules-are-about-communication&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;In fact, I think the entirety of the four rules is secretly about communication: write tests to communicate, remove duplication to make sure you&#x27;re not saying the same thing twice, remove any unnecessary distractions, and here where we&#x27;re being pretty explicit about intention!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;process-data-inspiration&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Example inspired by Corey Haines&#x27; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;leanpub.com&#x2F;4rulesofsimpledesign&quot;&gt;&lt;em&gt;Understanding the Four Rules of Simple Design&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Rule 1: Simplify When the Program Works</title>
		<published>2021-06-22T00:00:00+00:00</published>
		<updated>2021-06-22T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/rule-1-simplify-when-the-program-works/" type="text/html"/>
		<id>https://bytes.zone/posts/rule-1-simplify-when-the-program-works/</id>
		<content type="html">&lt;p&gt;Last week, I introduced &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;my-take-on-the-four-rules-of-simple-design&#x2F;&quot;&gt;My Take on the Four Rules of Simple Design&lt;&#x2F;a&gt;.
To sum up, I&#x27;ve found Kent Beck&#x27;s four rules of simple design really interesting to think about, and I wanted to come up with my own formulation!&lt;&#x2F;p&gt;
&lt;p&gt;This week, we meet the first rule: &lt;strong&gt;simplify when the program works.&lt;&#x2F;strong&gt;
It&#x27;s similar to the original first rule (&quot;runs all the tests&quot;), but with a broader reach.
So in addition to passing tests we might include things like:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Is it possible to get the program into an illegal or &quot;impossible&quot; state by using it?&lt;&#x2F;li&gt;
&lt;li&gt;Do the user-facing parts of the program look the way that the designer intended?&lt;&#x2F;li&gt;
&lt;li&gt;Is the program accessible to all kinds of users?
For example, can the program be operated using only a keyboard?
Only a mouse?
A screen reader?
(Broadly, Is the program &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.w3.org&#x2F;WAI&#x2F;WCAG21&#x2F;Understanding&#x2F;intro#understanding-the-four-principles-of-accessibility&quot;&gt;Perceivable, Operable, Understandable, and Robust&lt;&#x2F;a&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;Has QA tested this?
What did they think?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Some of those might not apply to your project, and some of them have really long iteration times, but that&#x27;s actually part of the point: if you find that your program is broken in some way, it probably makes sense to fix it before trying to make it faster or organize the code better.
Unit tests are just one way of looking at this, although certainly an attractive one since they can be automated.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-happens-if-you-ignore-this&quot;&gt;What Happens If You Ignore This?&lt;&#x2F;h2&gt;
&lt;p&gt;What happens if you start simplifying the code before the program works?&lt;&#x2F;p&gt;
&lt;p&gt;Well, when I&#x27;ve ignored this advice, I notice I tend to start ripping out useful code or changing things in ways that don&#x27;t make sense.
I end up totally muddling my code with unrelated changes, and despite all the effort the program &lt;em&gt;still&lt;&#x2F;em&gt; doesn&#x27;t work right.
Eventually, I&#x27;ve maybe made something kinda-sorta like the change I tried to make, but it&#x27;s way harder for my team to review.&lt;&#x2F;p&gt;
&lt;p&gt;In my experience, I need to be in one of two modes: making changes to make the program work, or making changes to make the code simpler.
&quot;Simplify when the program works&quot; has been helpful for me in recognizing when I need to switch!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;avoiding-perfectionism&quot;&gt;Avoiding Perfectionism&lt;&#x2F;h2&gt;
&lt;p&gt;But what happens if the vagueness in this rule works against you?
For example, you can pile so many things on to &quot;works&quot; that it becomes &quot;perfect.&quot;
Setting an unattainable standard means never shipping, and we&#x27;d like to at least ship &lt;em&gt;some day&lt;&#x2F;em&gt;, so let&#x27;s try to avoid perfectionism, right?&lt;&#x2F;p&gt;
&lt;p&gt;One way around this is to change your relationship to your standards, at least temporarily.
Something doesn&#x27;t have to be &lt;em&gt;perfect&lt;&#x2F;em&gt; to be &lt;em&gt;working&lt;&#x2F;em&gt;.
For example, maybe messy code is fine &lt;em&gt;for now&lt;&#x2F;em&gt;, or maybe it&#x27;s OK for some tests to fail—just for a bit—while you work on accessibility in the UI.&lt;&#x2F;p&gt;
&lt;p&gt;Said differently, we can take on tech debt to avoid perfectionism.&lt;&#x2F;p&gt;
&lt;p&gt;We usually want to avoid that, and you might have some &quot;yuck!&quot; reaction to reading it, but there are places where it makes sense.
I think &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;yvonnezlam&quot;&gt;Yvonne Lam&lt;&#x2F;a&gt; really nailed it with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;yvonnezlam&#x2F;status&#x2F;1376631868972433408&quot;&gt;her concept of tech debt as housework&lt;&#x2F;a&gt;, specifically:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The thing with tech debt is that in order to have a useful discussion, you need to be able to talk about *who* is affected and how -- who is going to be woken up, be pulled off other work, be unable to perform some critical function, etc. You can&#x27;t hide risk and have that talk.&lt;&#x2F;p&gt;
&lt;p&gt;— &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;yvonnezlam&#x2F;status&#x2F;1376631868972433408&quot;&gt;Yvonne Lam on Twitter&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Let&#x27;s apply that: who is going to be affected by changing your standards, and how?
This will be different in different settings, so asking these questions with your team makes a lot of sense.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, I care a lot about accessibility.
I don&#x27;t want to ship software that prevents people from using assistive technology like screen readers.
In my company&#x27;s case, our users are kids learning to write better.
I really care about not messing that up!&lt;&#x2F;p&gt;
&lt;p&gt;My team shares that value, so when we found out in a recent project that we&#x27;d have to choose between having more complicated view code versus shipping a program that was inaccessible to screen readers, we chose the complex code.
We know we&#x27;re going to have to clean up the code sometime, but for now we&#x27;re OK with maintaining some slightly complicated code to make sure we&#x27;re accommodating everyone&#x27;s needs.&lt;&#x2F;p&gt;
&lt;p&gt;Because we&#x27;d already had the conversation about what values we wanted our code to embody, we were able to make a choice that lined up with those values.
Even though the software wasn&#x27;t perfect, I think we made the right tradeoff.&lt;&#x2F;p&gt;
&lt;p&gt;Point is: talk with your team about this!
Every project, team, and person has a different set of values, so trying to nail down what &quot;works&quot; means can highlight differences in how we think about the things we&#x27;re building.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;make-the-change-easy&quot;&gt;Make The Change Easy&lt;&#x2F;h2&gt;
&lt;p&gt;Another way to apply this rule is to &quot;make the change easy, then make the easy change.&quot;
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;kentbeck&#x2F;status&#x2F;250733358307500032&quot;&gt;Coincidentally, another Kent Beck-ism&lt;&#x2F;a&gt;.)
In other words, when you go to your code to make a change, consider refactoring towards simpler code &lt;em&gt;first&lt;&#x2F;em&gt; instead of waiting until after you&#x27;re done.&lt;&#x2F;p&gt;
&lt;p&gt;It might seem like this is incompatible with the rule—after all, if you&#x27;re planning to make a change you already know the program doesn&#x27;t work according to the specification—but we often have to do some cleanup in order to make the change possible!
For the purposes of this rule, &quot;works&quot; can also mean &quot;is amenable to change!&quot;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;So, to sum up:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Simplify when the program works&quot; depends a lot on what you mean by &quot;works.&quot;
(It also depends on what you mean by &quot;simplify&quot;, but that&#x27;s what the other rules are for—coming soon!)&lt;&#x2F;li&gt;
&lt;li&gt;This rule can be helpful as a signal to switch from writing-code-to-make-the-program-work mode to clarifying-code-for-future-us mode.&lt;&#x2F;li&gt;
&lt;li&gt;Set a high bar, but no so high that you can&#x27;t ever ship.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;See you next week for rule 2: clarify your intent.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>My Take on the Four Rules of Simple Design</title>
		<published>2021-06-15T00:00:00+00:00</published>
		<updated>2021-06-15T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/my-take-on-the-four-rules-of-simple-design/" type="text/html"/>
		<id>https://bytes.zone/posts/my-take-on-the-four-rules-of-simple-design/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been thinking about how to write better tests recently, and I happened on Kent Beck&#x27;s four rules for simple design.
In a nutshell, these rules were meant to give objective criteria for the question &quot;is this code simple?&quot;
The original formulation goes like this: a piece of code can be said to be simple if it...&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Runs all the tests.&lt;&#x2F;li&gt;
&lt;li&gt;Has no duplicated logic. Be wary of hidden duplication like parallel class hierarchies&lt;&#x2F;li&gt;
&lt;li&gt;States every intention important to the programmer.&lt;&#x2F;li&gt;
&lt;li&gt;Has the fewest possible classes and methods.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;— &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.google.com&#x2F;books&#x2F;edition&#x2F;Extreme_Programming_Explained&#x2F;G8EL4H4vf7UC?hl=en&amp;amp;gbpv=1&amp;amp;pg=PA57&amp;amp;printsec=frontcover&quot;&gt;Kent Beck, &lt;em&gt;Extreme Programming Explained: Embrace Change&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;In practice, I really like that while the intention may have been to make an objective metric for simplicity, people tend to treat these rules as &lt;em&gt;imperfect but useful&lt;&#x2F;em&gt;.
That is, they give us a way to judge if a piece of code is better or worse than another one.
This turns out to be a helpful way of judging internal quality of a piece of software—and improving that is why we write tests and refactor code in the first place!&lt;&#x2F;p&gt;
&lt;p&gt;I think this usefulness has made these rules surprisingly uncontroversial (at least to me, based on how much I know we programmers like bikeshedding!)
The most controversial topic, in fact, seems to be which order to apply rules 2 and 3 in.&lt;&#x2F;p&gt;
&lt;p&gt;In reading about this, I collected a bunch of interpretations of the four rules from various authors:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Rule&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: left&quot;&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.google.com&#x2F;books&#x2F;edition&#x2F;Extreme_Programming_Explained&#x2F;G8EL4H4vf7UC?hl=en&amp;amp;gbpv=1&amp;amp;pg=PA57&amp;amp;printsec=frontcover&quot;&gt;Kent Beck&lt;&#x2F;a&gt;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: left&quot;&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.martinfowler.com&#x2F;bliki&#x2F;BeckDesignRules.html&quot;&gt;Martin Fowler&lt;&#x2F;a&gt;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: left&quot;&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.jbrains.ca&#x2F;permalink&#x2F;the-four-elements-of-simple-design&quot;&gt;J.B. Rainsberger&lt;&#x2F;a&gt;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: left&quot;&gt;&lt;a rel=&quot;external&quot; title=&quot;n.b. I switched rules 2 and 3 in Fowler&#x27;s and Haines&#x27; formulations for reading consistency.
Based on these author&#x27;s writings, I think it&#x27;s fine in this context.&quot; href=&quot;https:&#x2F;&#x2F;leanpub.com&#x2F;4rulesofsimpledesign&quot;&gt;Corey Haines&lt;&#x2F;a&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Runs all the tests.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Passes the tests&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Passes its tests&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Tests Pass&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Has no duplicated logic. […]&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;No duplication&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Minimizes Duplication&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;No Duplication (DRY)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;States every intention important to the programmer.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Reveals intention&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Maximizes clarity&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Expresses Intent&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Has the fewest possible classes and methods.&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Fewest elements&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Has fewer elements&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Small&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;I have enjoyed applying these rules to my own code, but in the spirit of &quot;what I cannot create, I do not understand,&quot; I thought it&#x27;d be fun to try and make my own formulation.
In doing this, I&#x27;d like to avoid simply paraphrasing the four rules—I want to be able to explain what makes code simple to someone who has never heard of this concept.
That means being able to make a reasonable argument for each point, as well as talking about the consequences of leaving them off.&lt;&#x2F;p&gt;
&lt;p&gt;I originally thought that&#x27;d be one post, but then I did a bunch of research to make sure I wasn&#x27;t just making stuff up and it turned out super long.
So here&#x27;s what we&#x27;re going to do: over the next month, I&#x27;ll be publishing one post per week about a new rule.
When I do that, I&#x27;ll come back to this post and link them up.
If you&#x27;re here after July 2021, welcome!
Get started by clicking the links below.
Otherwise, you can sign up to be notified about new posts by putting your email in the box at the bottom of the post.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-1-simplify-when-the-program-works&#x2F;&quot;&gt;Simplify When the Program Works&lt;&#x2F;a&gt; (June 22)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-2-clarify-your-intent&#x2F;&quot;&gt;Clarify Your Intent&lt;&#x2F;a&gt; (June 29)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-3-centralize-behavior&#x2F;&quot;&gt;Centralize Behavior&lt;&#x2F;a&gt; (July 6)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;rule-4-code-for-now&#x2F;&quot;&gt;Code for Now&lt;&#x2F;a&gt; (July 13)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Afterwards, I&#x27;ll probably publish a conclusion including reader feedback in late July.
I&#x27;d love to hear from you as you&#x27;re reading these things; you can email me at &lt;a href=&quot;mailto:brian@brianthicks.com&quot;&gt;brian@brianthicks.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>rbt</title>
		<published>2021-06-07T00:00:00+00:00</published>
		<updated>2022-12-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/rbt/" type="text/html"/>
		<id>https://bytes.zone/projects/rbt/</id>
		<content type="html">&lt;p&gt;From early June 2021 to December 2022, I&#x27;ve been working off and on on a tool called rbt (the Roc Build Tool.)
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;roc-lang&#x2F;rbt&quot;&gt;All the work is open source&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I learned a lot from building rbt!
Enough, anyway, to know that to get the things I wanted out of a build system I&#x27;d need a different approach.
Long story short, that&#x27;s why I started working on &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;projects&#x2F;bold&#x2F;&quot;&gt;bold&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Trying (and Failing) to Speed Up String.startsWith</title>
		<published>2021-03-01T00:00:00+00:00</published>
		<updated>2021-03-01T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/trying-and-failing-to-speed-up-string-beginswith/" type="text/html"/>
		<id>https://bytes.zone/posts/trying-and-failing-to-speed-up-string-beginswith/</id>
		<content type="html">&lt;p&gt;When I was optimizing &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;elm-csv-package-and-talk&#x2F;&quot;&gt;elm-csv&lt;&#x2F;a&gt;, I noticed that Elm&#x27;s &lt;code&gt;String.startsWith&lt;&#x2F;code&gt; is implemented using JavaScript&#x27;s &lt;code&gt;indexOf&lt;&#x2F;code&gt; method.
It looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; startsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;haystack&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; needle&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; haystack.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;indexOf&lt;&#x2F;span&gt;&lt;span&gt;(needle)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This strikes me as inefficient!
Doesn&#x27;t that mean that if &lt;code&gt;haystack&lt;&#x2F;code&gt; doesn&#x27;t begin with &lt;code&gt;needle&lt;&#x2F;code&gt;, it&#x27;ll still look through the whole string?
That seems to waste a lot of cycles on work after we already know the result.&lt;&#x2F;p&gt;
&lt;p&gt;After thinking about it for a little bit, I imagined a faster way:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; startsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;haystack&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; needle&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; haystack.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;slice&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, needle.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span&gt; needle;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But, I didn&#x27;t know for sure if it would be faster, so I started measuring things.
After a few benchmarks things were looking promising!
I found that &lt;code&gt;String.prototype.slice&lt;&#x2F;code&gt; and &lt;code&gt;String.prototype.length&lt;&#x2F;code&gt; operate in more-or-less constant time, and that &lt;code&gt;===&lt;&#x2F;code&gt;, while not constant-time, is heavily optimized.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Hypothetically&lt;&#x2F;em&gt;, I thought, this means that a &lt;code&gt;slice&lt;&#x2F;code&gt;-based implementation of &lt;code&gt;startsWith&lt;&#x2F;code&gt; could run much quicker than scanning through the whole string with &lt;code&gt;indexOf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chrome-uses-actual-magic-in-their-strings&quot;&gt;Chrome Uses Actual Magic in Their Strings&lt;&#x2F;h2&gt;
&lt;p&gt;But just to make sure my intuition about &lt;code&gt;indexOf&lt;&#x2F;code&gt; being slow was correct, I decided to benchmark that as well.
Specifically, I decided to benchmark how long browsers took to find a single character in a string that doesn&#x27;t contain the character:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;abc&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;repeat&lt;&#x2F;span&gt;&lt;span&gt;(size).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;indexOf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;d&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I expected that every time I multiplied &lt;code&gt;size&lt;&#x2F;code&gt; by 10, I should see roughly a 10x slowdown in operations per second.
But then…&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;1 &quot;abc&quot;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;10 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;100 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;1,000 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chrome 88.0.4324.182&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;866,026,834&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;869,256,882&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;858,563,862&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;865,370,315&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Firefox 85.0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;43,557,275&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;44,947,174&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;41,059,919 (-8.65%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;16,831,928 (-59.01%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Safari 14.0.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;49,240,581&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;34,661,639 (-29.61%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7,162,615 (-79.01%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;877,449 (-87.75%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Chrome has… NO REAL SLOWDOWN?
WHAT?
This data suggests that Chrome can find a substring in any length string in constant time.
How is that even possible?&lt;&#x2F;p&gt;
&lt;p&gt;At this point, I felt pretty out of my depth, and decided I needed to ask my friend &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lukewestby&quot;&gt;Luke Westby, Real-Life Computer Genius™&lt;&#x2F;a&gt;, what was going on.
He knew right where to look and figured out that &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;v8&#x2F;v8&#x2F;blob&#x2F;dc712da548c7fb433caed56af9a021d964952728&#x2F;src&#x2F;strings&#x2F;string-search.h#L194&quot;&gt;Chrome has a special optimization for single-character search strings in &lt;code&gt;indexOf&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
In other words, people have hand-tuned V8 over the years to go ridiculously fast in all sorts of situations, and this is one of them.&lt;&#x2F;p&gt;
&lt;p&gt;Well, OK, let&#x27;s try finding some multi-character string instead.
I used &quot;nope&quot;, which doesn&#x27;t trigger this optimization, but still fails to match &quot;abc&quot;.
Does that give us a result that matches my intuition?&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;1 &quot;abc&quot;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;10 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;100 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;1,000 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chrome 88.0.4324.182&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;885,211,831&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;863,845,523 (-2.41%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;849,821,335 (-1.62%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;837,391,727 (-1.46%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Firefox 85.0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;58,648,716&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;44,107,472 (-24.79%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;40,529,513 (-8.11%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;16,768,019 (-58.63%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Safari 14.0.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;102,071,445&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;26,242,114 (-74.29%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4,212,339 (-83.95%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;467,329 (-88.91%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;That&#x27;s &lt;em&gt;kinda&lt;&#x2F;em&gt; like what I expected, but Chrome is still incredibly fast at this!
Well, OK, that&#x27;s fine.
Good for them.
But at this point I got curious (and a little devious): how can I defeat Chrome&#x27;s optimizations?
What is the worst-case scenario that they can&#x27;t possibly have handled already?&lt;&#x2F;p&gt;
&lt;p&gt;I tried to imagine the algorithm that you&#x27;d have to write to implement &lt;code&gt;indexOf&lt;&#x2F;code&gt; and came up with something like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Look at the first character in &lt;code&gt;haystack&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;If it matches the first character in &lt;code&gt;needle&lt;&#x2F;code&gt;, look at the next pairs of characters in the two strings one by one.&lt;&#x2F;li&gt;
&lt;li&gt;If you get to the end of &lt;code&gt;needle&lt;&#x2F;code&gt; and they all match, you&#x27;ve found the index.&lt;&#x2F;li&gt;
&lt;li&gt;But if one pair doesn&#x27;t match, you&#x27;ve got to start over at the next character in &lt;code&gt;haystack&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;With that in mind, what&#x27;s combination of &lt;code&gt;needle&lt;&#x2F;code&gt; and &lt;code&gt;haystack&lt;&#x2F;code&gt; would perform the worst?
I think it&#x27;s any pair of strings where &lt;code&gt;haystack&lt;&#x2F;code&gt; is all but the last character of &lt;code&gt;needle&lt;&#x2F;code&gt; repeated over and over.
So, maybe a benchmark like this?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;abc&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;repeat&lt;&#x2F;span&gt;&lt;span&gt;(size).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;indexOf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;abcd&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That can never succeed, but it&#x27;ll make the browser do a lot of extra work reading the initially-matching &quot;abc&quot; over and over.
Let&#x27;s see the results:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;1 &quot;abc&quot;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;10 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;100 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;1,000 &quot;abc&quot;s&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chrome 88.0.4324.182&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;877,221,230&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;863,929,216 (-1.52%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;688,398,676 (-20.32%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;125,023 (-99.98%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Firefox 85.0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;61,384,762&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;12,874,505 (-79.03%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,366,202 (-89.39%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;137,245 (-89.95%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Safari 14.0.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;100,546,438&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;25,948,095 (-74.19%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4,170,375 (-83.93%)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;467,753 (-88.78%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Finally, Chrome has similar performance to other browsers.
I don&#x27;t think this is representative of real workloads, but even so Chrome performs admirably.
Hats off to the V8 team for this implementation, but honestly, it&#x27;s good to know they&#x27;re still mortal!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comparing-the-implementations&quot;&gt;Comparing the Implementations&lt;&#x2F;h2&gt;
&lt;p&gt;So now that we know more about the component pieces involved in this optimization (for example, knowing to avoid single-character test strings), we can check if &lt;code&gt;slice&lt;&#x2F;code&gt; actually gives us a real-world speedup.
In setting this up, I decided to go for the middle of the road to try and benchmark the browsers under something that you can kind of squint at and think &quot;yeah, that will probably happen reasonably often in real life.&quot;
In this case, that meant setup looked like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;haystack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;abc&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;repeat&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;needle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;abc&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then the &lt;code&gt;indexOf&lt;&#x2F;code&gt; and &lt;code&gt;slice&lt;&#x2F;code&gt; benchmarks looked like the code at the top of this post:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;haystack.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;indexOf&lt;&#x2F;span&gt;&lt;span&gt;(needle)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;haystack.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;slice&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, needle.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span&gt; needle;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That gave me these results:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;indexOf&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;slice&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;% Diff&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chrome 88.0.4324.192&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;871,312,454&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;870,810,291&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-0.06%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Firefox 86.0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;32,428,394&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;46,845,953&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+44.46%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Safari 14.0.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;430,854,455&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,352,674,261&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+213.95%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Note that Chrome and Firefox had released new versions between me testing &lt;code&gt;indexOf&lt;&#x2F;code&gt; and this test. I kept the old data here because it doesn&#x27;t seem to have changed the performance, but I just want to be up front that the versions are different!&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, that said: looks like a result within the margin of error for Chrome (around 0.4%), but a reasonable speedup for Firefox and a big win for Safari.&lt;&#x2F;p&gt;
&lt;p&gt;What does the failure case look like? (Same &lt;code&gt;haystack&lt;&#x2F;code&gt;, but &lt;code&gt;needle = &quot;nope&quot;&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;indexOf&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;slice&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;% Diff&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chrome 88.0.4324.192&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;874,951,212&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;854,116,367&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-2.44%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Firefox 85.0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;31,026,729&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;22,180,114&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-28.51%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Safari 14.0.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4,189,768&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,508,770,435&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+35,910.83%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;This time, Chrome has a slowdown greater than the measurement error, which surprised me!
Same with Firefox, but to a larger degree.
But the real story here is Safari, which can now do this operation over 1.5 billion times per second.
&lt;em&gt;Billion!&lt;&#x2F;em&gt;
What even!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;is-it-worth-it&quot;&gt;Is It Worth It?&lt;&#x2F;h2&gt;
&lt;p&gt;So, with all that measurement done, is this optimization worth it?
I&#x27;m actually not really sure.
This is basically nothing in Chrome but means making some real performance tradeoffs in Firefox.
And, even though it looks like a huge win in Safari, my claims can only be as strong as the underlying assumptions I&#x27;m making in choosing test data.
So, just to be explicit about that:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;These benchmarks assume that &lt;code&gt;haystack&lt;&#x2F;code&gt; is a long-ish string (300 characters.)
Many of the gains we see here are erased when the strings are short.
Safari in particular, drops to only about a 100% gain in the failure case with a shorter string (I tested at 3, 30, 150 characters, all with similar results.)&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;code&gt;needle&lt;&#x2F;code&gt; strings are neither a single character nor a worst-case scenario for &lt;code&gt;indexOf&lt;&#x2F;code&gt; performance.&lt;&#x2F;li&gt;
&lt;li&gt;Success and failure cases are equally likely.
We can&#x27;t make any assumption other than that at the runtime level, although we could make a good argument to switch this for individual applications.
For example, we could choose to use &lt;code&gt;slice&lt;&#x2F;code&gt; in an application where we expected most checks on &lt;code&gt;startsWith&lt;&#x2F;code&gt; would succeed, since that&#x27;s basically a wash in Chrome and much faster in Firefox and Safari.&lt;&#x2F;li&gt;
&lt;li&gt;We care about browsers based on global total share instead of splitting out into desktop or mobile.
This is another area where you could make different decisions at the application level: if the users of your app skew more towards using Chrome or more towards Safari, this payoff for this optimization gets better or worse.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Of course, if you can measure that your application &lt;em&gt;would&lt;&#x2F;em&gt; benefit from this optimization, go ahead and do it yourself!
Calling &lt;code&gt;String.slice 0 (String.length haystack) === needle&lt;&#x2F;code&gt; has some overhead in Elm (due to equality having different semantics) but you may still able to get the majority of the benefits if your users are mostly using Safari or you&#x27;re running iOS webkit views or something like that.
Just make sure to actually measure it!
Optimizations at this level can be helpful, but they&#x27;re more often dwarfed by DOM stuff in the runtime.&lt;&#x2F;p&gt;
&lt;p&gt;As for me: at this point, I&#x27;m not going to make a PR to &lt;code&gt;elm&#x2F;core&lt;&#x2F;code&gt; with this improvement, even though I intended to when I set out.
However, I found it interesting to look into these things, and I learned a lot about how strings are optimized in various browsers, so I&#x27;m gonna call it a win overall.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>nix-script</title>
		<published>2021-02-23T00:00:00+00:00</published>
		<updated>2021-02-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/nix-script/" type="text/html"/>
		<id>https://bytes.zone/posts/nix-script/</id>
		<content type="html">&lt;p&gt;I like writing quick little scripts to avoid having to remember how to do things.
Most of the time I start in bash, thinking the task won&#x27;t be too complicated... but then before I know it I&#x27;m having to reread the bash man pages to figure out how arrays work for the thousandth time.&lt;&#x2F;p&gt;
&lt;p&gt;So really, I&#x27;d rather write scripts in a language that offers some more safety and better data structures.
We&#x27;re trying to learn Haskell at work, so maybe that?&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s see a hello world:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!&#x2F;usr&#x2F;bin&#x2F;env runghc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; :: IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;main &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; putStrLn &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Hello, World!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That seems pretty reasonable, and only takes like 300ms to compile and run.
Not awful, but it&#x27;s not as fast as a bash script, and it means I have to use Haskell&#x27;s standard prelude instead of something safer like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kowainik.github.io&#x2F;projects&#x2F;relude&quot;&gt;relude&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;nri-prelude&quot;&gt;nri-prelude&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s reasonable to solve this, though: I can just use &lt;code&gt;nix-shell&lt;&#x2F;code&gt; to get a &lt;code&gt;ghc&lt;&#x2F;code&gt; with packages.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!&#x2F;usr&#x2F;bin&#x2F;env nix-shell&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!nix-shell -p &amp;quot;(pkgs.haskellPackages.ghcWithPackages (ps: [ ps.text ]))&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!nix-shell -i runghc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{-#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LANGUAGE OverloadedStrings&lt;&#x2F;span&gt;&lt;span&gt; #-}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Data.Text.IO&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; :: IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;main &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Data.Text.IO.&lt;&#x2F;span&gt;&lt;span&gt;putStrLn &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Hello, World!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well, that works, but I&#x27;ve traded speed for flexibility: run time has grown to over 2 seconds!
Eep!&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t think I should have to make this trade, so I wrote &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;nix-script&quot;&gt;&lt;code&gt;nix-script&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
It transparently manages a compilation cache for these kinds of scripts, and lets you specify dependencies and build commands inside your source file!&lt;&#x2F;p&gt;
&lt;p&gt;That means the &lt;code&gt;nix-shell&lt;&#x2F;code&gt; example above can be rewritten like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!&#x2F;usr&#x2F;bin&#x2F;env nix-script&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!buildInputs (pkgs.haskellPackages.ghcWithPackages (ps: [ ps.text ]))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!build ghc -O -o $OUT_FILE $SCRIPT_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{-#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LANGUAGE OverloadedStrings&lt;&#x2F;span&gt;&lt;span&gt; #-}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Data.Text.IO&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; :: IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;main &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Data.Text.IO.&lt;&#x2F;span&gt;&lt;span&gt;putStrLn &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Hello, World!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first time you run that, it&#x27;ll compile the script to a binary, then run it.
That takes about two seconds on my machine.
The second time, and going forward until you change the script, it detects that it already compiled to a binary, so it just runs the compiled version.
That takes 30ms or so for me!
Big improvement!&lt;&#x2F;p&gt;
&lt;p&gt;But it&#x27;s really not that fun to have to figure out that &lt;code&gt;#!build&lt;&#x2F;code&gt; line every time, and I always forget how to call &lt;code&gt;pkgs.haskellPackages.ghcWithPackages&lt;&#x2F;code&gt; correctly... so there&#x27;s also a wrapper script called &lt;code&gt;nix-script-haskell&lt;&#x2F;code&gt; that makes this nicer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!&#x2F;usr&#x2F;bin&#x2F;env nix-script-haskell&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!haskellPackages text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{-#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LANGUAGE OverloadedStrings&lt;&#x2F;span&gt;&lt;span&gt; #-}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Data.Text.IO&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; :: IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;main &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Data.Text.IO.&lt;&#x2F;span&gt;&lt;span&gt;putStrLn &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Hello, World!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And, in addition to the speed boost, we can depend on any package in the Nix ecosystem!
For example, here&#x27;s how you&#x27;d add and call &lt;code&gt;jq&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!&#x2F;usr&#x2F;bin&#x2F;env nix-script-haskell&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#!runtimeInputs jq&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; System.Process&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; :: IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;main &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;= do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  formatted &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;-&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    readProcess&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;      &amp;quot;jq&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;--color-output&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;      &amp;quot;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;Atlas&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;species&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;kitty cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  putStr formatted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s also pretty easy to create more wrapping interpreters, so we also ship one for bash.
Even though it&#x27;s not a compiled language, we can cache the nix environment with the exact dependencies the script needs!&lt;&#x2F;p&gt;
&lt;p&gt;You can get this at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;nix-script&quot;&gt;github.com&#x2F;BrianHicks&#x2F;nix-script&lt;&#x2F;a&gt;.
There are installation instructions in the README, both for standalone use and use within a larger Nix project.&lt;&#x2F;p&gt;
&lt;p&gt;Enjoy, and let me know if—and how—you use this!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elm-csv, package and talk</title>
		<published>2021-02-15T00:00:00+00:00</published>
		<updated>2021-02-15T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/elm-csv-package-and-talk/" type="text/html"/>
		<id>https://bytes.zone/posts/elm-csv-package-and-talk/</id>
		<content type="html">&lt;p&gt;In the beginning of February, I spent a lot of time working on a new library for parsing CSV data in Elm.
(Basically, the existing libraries did not have &lt;code&gt;andThen&lt;&#x2F;code&gt; and I needed it!)&lt;&#x2F;p&gt;
&lt;p&gt;The resulting library turned out quite nice, I think, and in addition it&#x27;s very fast!
I gave talk to the Elm London user group about it, which you can see at &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;talks&#x2F;elm-csv-be-as-boring-and-as-fast-as-possible&#x2F;&quot;&gt;elm-csv: be as boring—and as fast—as possible&lt;&#x2F;a&gt; or below.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d just like to see the code, it lives at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;elm-csv&quot;&gt;github.com&#x2F;BrianHicks&#x2F;elm-csv&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;figure class=&quot;youtube-embed&quot;&gt;
  &lt;div&gt;
    &lt;iframe src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;_z6V1143TDE&quot; title=&quot;elm-csv: be as boring—and as fast—as possible&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen frameborder=0&gt;&lt;&#x2F;iframe&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elm-csv: be as boring—and as fast—as possible</title>
		<published>2021-02-09T00:00:00+00:00</published>
		<updated>2021-02-09T00:00:00+00:00</updated>
		<link href="https://bytes.zone/talks/elm-csv-be-as-boring-and-as-fast-as-possible/" type="text/html"/>
		<id>https://bytes.zone/talks/elm-csv-be-as-boring-and-as-fast-as-possible/</id>
		<content type="html">&lt;figure class=&quot;youtube-embed&quot;&gt;
  &lt;div&gt;
    &lt;iframe src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;_z6V1143TDE&quot; title=&quot;elm-csv: be as boring—and as fast—as possible&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen frameborder=0&gt;&lt;&#x2F;iframe&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elm-csv</title>
		<published>2021-01-19T00:00:00+00:00</published>
		<updated>2021-11-23T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/elm-csv/" type="text/html"/>
		<id>https://bytes.zone/projects/elm-csv/</id>
		<content type="html">&lt;p&gt;In early 2021, I was working on a page at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;noredink.com&quot;&gt;NoRedInk&lt;&#x2F;a&gt; that let our curriculum team import big batches of updates to premade assignments for teachers to use.
The page was taking &lt;em&gt;for-ev-er&lt;&#x2F;em&gt; to work since the CSV parser we were using at the time was fairly slow.
I decided to fix it that!&lt;&#x2F;p&gt;
&lt;p&gt;I ended up publishing the resulting code as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BrianHicks&#x2F;elm-csv&quot;&gt;BrianHicks&#x2F;elm-csv&lt;&#x2F;a&gt;, a CSV decoding package that works like Elm&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;package.elm-lang.org&#x2F;packages&#x2F;elm&#x2F;json&#x2F;latest&#x2F;&quot;&gt;JSON decoders&lt;&#x2F;a&gt;.
It&#x27;s super fast—there&#x27;s a lot of hand-rolled character-wise parsing work to thank for that—and at the time of publishing was the only CSV package for Elm that didn&#x27;t force you to separate character parsing from interpretation.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Should Elm files be long or short?</title>
		<published>2021-01-11T00:00:00+00:00</published>
		<updated>2021-01-11T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/should-elm-files-be-long-or-short/" type="text/html"/>
		<id>https://bytes.zone/posts/should-elm-files-be-long-or-short/</id>
		<content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;guide.elm-lang.org&#x2F;webapps&#x2F;structure.html&quot;&gt;the Elm Guide page on app Structure&lt;&#x2F;a&gt; says:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;[Don&#x27;t] Prefer shorter files.
In JavaScript, the longer your file is, the more likely you have some sneaky mutation that will cause a really difficult bug.
But in Elm, that is not possible!
Your file can be 2000 lines long and that still cannot happen.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Someone &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discourse.elm-lang.org&#x2F;t&#x2F;should-i-prefer-big-elm-files&#x2F;6687&quot;&gt;asked about this on the Elm Discourse&lt;&#x2F;a&gt; recently, wondering if this advice meant they should prefer longer Elm files over shorter ones.
I thought this was a really interesting question!
I answered on Discourse, but I also want to clean up my answer to share a little wider.
Here goes!&lt;&#x2F;p&gt;
&lt;p&gt;So, personally, I don&#x27;t really agree with that part of the docs.
I&#x27;ve worked in codebases with short files and longer files, and it didn&#x27;t seem like either style was more or less prone to mutation bugs.
That makes me think that &lt;strong&gt;file length is not the problem, mutation is.&lt;&#x2F;strong&gt;
And mutation is not a problem in Elm—it&#x27;s just not allowed in the language at all, regardless of file length!&lt;&#x2F;p&gt;
&lt;p&gt;What does that mean for how we write Elm?
Should Elm files be longer or shorter than JavaScript files?
Well, neither really!
If we accept that file length is not strongly correlated with this kind of bug, it doesn&#x27;t matter!
Elm files &lt;strong&gt;may&lt;&#x2F;strong&gt; be longer as a consequence of the things I&#x27;m gonna say below, but that&#x27;s a consequence only—not a goal!&lt;&#x2F;p&gt;
&lt;p&gt;Now, with that out of the way, we can get to the more interesting question: what &lt;em&gt;is&lt;&#x2F;em&gt; the right file length?
I think to answer that we need to figure out what a module is actually good for.
In Elm, I use modules for two reasons:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;encapsulation:&lt;&#x2F;strong&gt; hiding or exposing bits of the implementation to make a cohesive API.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;namespacing:&lt;&#x2F;strong&gt; defining things like &lt;code&gt;toString&lt;&#x2F;code&gt; under different name spaces.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The second is &lt;em&gt;kiiiinda&lt;&#x2F;em&gt; a consequence of the first, so I think we can squint and ignore it.
So for our purposes, I&#x27;m asserting that files are for encapsulation.
That means &lt;strong&gt;the right file length is the one where your encapsulation works to prevent bugs&lt;&#x2F;strong&gt;, either by preventing encapsulation-violating behavior or making your code easy enough to understand that you can verify it&#x27;s doing the right thing.
That doesn&#x27;t mean long files or short files, but correct-for-the-situation files.&lt;&#x2F;p&gt;
&lt;p&gt;I realize that this basically boils down to &quot;it depends&quot;, which I know is a bit unsatisfying.
Sorry!
Maybe it will help if I share some assorted things I use to know if a module needs to be broken up (or not):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Yes, split if:&lt;&#x2F;strong&gt; there&#x27;s a function that should not be able to access internals of a data structure, but which can because it lives in the same module.
Of course, the opposite is true too... if there&#x27;s a function that needs to be able to access internals but &lt;em&gt;can&#x27;t&lt;&#x2F;em&gt; the module boundary may be in the wrong place!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Yes, split if:&lt;&#x2F;strong&gt; there are two independent data structures in the same module.
One smell here is if I have to write &lt;code&gt;fooToString&lt;&#x2F;code&gt; and &lt;code&gt;barToString&lt;&#x2F;code&gt; in the same module, and &lt;code&gt;foo&lt;&#x2F;code&gt; and &lt;code&gt;bar&lt;&#x2F;code&gt; are independently valuable, it may be time to give at least one a new home.
The opposite (&quot;join if…&quot;) looks the same as the previous item: if you split a single data structure into two modules, you&#x27;ll frequently need to share too many internals and so should move everything into one file.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No, don&#x27;t split if:&lt;&#x2F;strong&gt; there is just &quot;too much code&quot; in a file.
I&#x27;ve split apps into &lt;code&gt;Model.elm&lt;&#x2F;code&gt;, &lt;code&gt;Update.elm&lt;&#x2F;code&gt;, &lt;code&gt;View.elm&lt;&#x2F;code&gt; and almost always regretted it.
Even if it&#x27;s a little tricky to navigate a long file, it&#x27;s way better than having to perform module gymnastics to prevent import loops.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All that said: like the rest of that page in the guide, I would encourage people to write long files and then break them up instead of making tiny files before it&#x27;s actually necessary.
I&#x27;ve been using Elm for something like half a decade now and the only reliable way I&#x27;ve found to put module boundaries in the right places is to wait and see what the right place &lt;em&gt;is&lt;&#x2F;em&gt;.
I suppose that means I am, in effect, advocating for long files over short ones but again: that&#x27;s a consequence, not a goal!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Learning Requires Effort</title>
		<published>2021-01-05T00:00:00+00:00</published>
		<updated>2021-01-05T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/learning-requires-effort/" type="text/html"/>
		<id>https://bytes.zone/posts/learning-requires-effort/</id>
		<content type="html">&lt;p&gt;I recently finished &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;takesmartnotes.com&quot;&gt;&lt;em&gt;How to Take Smart Notes&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.
The core idea I took away from the book is that &lt;em&gt;learning requires effort&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;two-kinds-of-effort&quot;&gt;Two Kinds of Effort&lt;&#x2F;h2&gt;
&lt;p&gt;I think people generally recognize that learning requires effort, but have an easier time applying that to physical activities than mental ones.
You don&#x27;t really go around assuming that Olympic athletes are just people who jumped out of bed the morning of the games and decided to win gold in ice dancing.
Instead, we recognize that athletes train hard for many years in order to reach the top of the field.&lt;&#x2F;p&gt;
&lt;p&gt;But when it comes to learning in more abstract ways, it&#x27;s somehow easier to forget that effort is necessary!
For example, I&#x27;ve made the mistake many times of assuming I learned something just because I reached the last page of a book.&lt;&#x2F;p&gt;
&lt;p&gt;With that in mind, I can think of at least two ways of putting in the effort to learn.
There are definitely more, though!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;How to Take Smart Notes&lt;&#x2F;em&gt; recommends using the Zettelkasten (slip-box) system &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zettelkasten.de&#x2F;posts&#x2F;overview&#x2F;&quot;&gt;covered&lt;&#x2F;a&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fastcompany.com&#x2F;90535318&#x2F;this-simple-but-powerful-analog-method-will-rocket-your-productivity&quot;&gt;many&lt;&#x2F;a&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;writingcooperative.com&#x2F;zettelkasten-how-one-german-scholar-was-so-freakishly-productive-997e4e0ca125&quot;&gt;places&lt;&#x2F;a&gt; this past summer.
In this method, &quot;effort&quot; looks like making notes by rephrasing and making connections to things you already know.
Educationally speaking, this is called elaboration.&lt;&#x2F;p&gt;
&lt;p&gt;Outside of note-taking, I see this in how programmers learn new tools and techniques.
When someone says they want to learn something, a common response goes &quot;well, make a small but useful project and see.&quot;
I&#x27;ve told people that many times myself without recognizing that I&#x27;m telling people to learn through effort.&lt;&#x2F;p&gt;
&lt;p&gt;These examples make &quot;learning requires effort&quot; seem pretty general to me, since it applies both in places I expected and places I didn&#x27;t.
That has the feel of a useful principle which I can use to judge if I&#x27;m learning or not!
Am I putting in effort?
If not, I&#x27;m probably not learning!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;someone-else-can-t-learn-for-you&quot;&gt;Someone Else Can&#x27;t Learn For You&lt;&#x2F;h2&gt;
&lt;p&gt;&quot;Learning requires effort&quot; also means that someone else can&#x27;t learn for you.
Think about it: if learning requires effort, and someone else is putting in the effort, then it&#x27;s not &lt;em&gt;you&lt;&#x2F;em&gt; doing the learning.
That&#x27;s another mistake I&#x27;ve made many times over the years!
It&#x27;s natural for me to try and find the perfect resource when what I actually need to just get into the work of effortful learning as quickly as I can.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve seen this show up in several ways.
The first is conflating building a collection with learning.
Where&#x27;s the learning effort in just collecting information from other people, whether papers, conference talks, or blog posts?
If you&#x27;re not putting in the effort to connect new stuff to what you already know, you&#x27;re just playing gotta-catch-em-all with PDFs!&lt;&#x2F;p&gt;
&lt;p&gt;The second is similar, but a little more specific: using other people&#x27;s flashcards.
I&#x27;ve seen quite a few places where you can download flashcard decks for various computer science concepts.
At first glace, that seems fine, but where&#x27;s the learning there?
It&#x27;s certainly a lot of &lt;em&gt;effort&lt;&#x2F;em&gt; to memorize a flashcard deck by rote, but it&#x27;s the kind of effort that makes you learn the deck, not the subject.
Making flashcards, on the other hand, can actually create connections as you think about how best to structure the subject in flashcard form.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;put-it-all-together&quot;&gt;Put It All Together&lt;&#x2F;h2&gt;
&lt;p&gt;So, if learning requires effort, how can we learn?
Simple: just put in the effort.&lt;&#x2F;p&gt;
&lt;p&gt;But, oops, it&#x27;s actually not so simple: &lt;em&gt;what&lt;&#x2F;em&gt; effort?
What&#x27;s right for you and for what you&#x27;re learning?
If you just thrash and flail, by trying things that are way beyond your current skill or level of understanding, you won&#x27;t actually learn much.
To be effective, the things you&#x27;re trying to learn have to be just beyond what you can do already (in the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Zone_of_proximal_development&quot;&gt;zone of proximal development&lt;&#x2F;a&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;You also have to make sure you&#x27;re learning in a way that you can actually apply your new knowledge.
That means working towards some level or standard of mastery, ideally one against which you can measure your progress.
(For example, being able to clear the bar at a certain level in the high jump.)&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s what a good teacher, mentor, or coach is for: someone who has helped other people learn the thing you&#x27;re trying to learn can give you good advice on the best places to apply your effort as well as a standard of mastery to grow towards.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Tracking and Lasting</title>
		<published>2020-12-29T00:00:00+00:00</published>
		<updated>2020-12-29T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/tracking-and-lasting/" type="text/html"/>
		<id>https://bytes.zone/posts/tracking-and-lasting/</id>
		<content type="html">&lt;p&gt;Just a short post to note that I&#x27;ve added a &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;colophon&#x2F;&quot;&gt;colophon&lt;&#x2F;a&gt; to this site.
It goes over how to get the source of this site (git), what I track (not much, and no third-party stuff), and how the page is designed to last.&lt;&#x2F;p&gt;
&lt;p&gt;I admit it&#x27;s pretty self-indulgent, but I wanted to be public about some of the things I&#x27;ve been thinking about while building this site.
Please feel free to &lt;a href=&quot;mailto:brian@brianthicks.com&quot;&gt;email me&lt;&#x2F;a&gt; any questions or comments about it!
I&#x27;d love to make it as clear as possible.&lt;&#x2F;p&gt;
&lt;p&gt;To repeat the link, that&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;colophon&#x2F;&quot;&gt;the colophon page&lt;&#x2F;a&gt;, and linked to from the footer.
Check it out!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>time-to-horse</title>
		<published>2020-12-22T00:00:00+00:00</published>
		<updated>2020-12-22T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/time-to-horse/" type="text/html"/>
		<id>https://bytes.zone/posts/time-to-horse/</id>
		<content type="html">&lt;p&gt;Some colleagues of mine at NoRedInk coined a new term: time-to-horse, the time it takes you to get on your horse and ride.&lt;&#x2F;p&gt;
&lt;p&gt;They used this in the context of their team getting familiar with a new area of our code, but I think it&#x27;s also useful for talking about how long it takes someone to make meaningful contributions when joining a team.&lt;&#x2F;p&gt;
&lt;p&gt;I think you probably want your time-to-horse to be as low as possible in either case.
There are a bunch of things that affect this (like documentation, pairing and collaboration practices, and continuous integration pipeline quality) but I think they all boil down to &quot;do you have fast feedback loops?&quot;&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Phantom ID Types</title>
		<published>2020-12-14T00:00:00+00:00</published>
		<updated>2020-12-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/reusable-phantom-id-types/" type="text/html"/>
		<id>https://bytes.zone/posts/reusable-phantom-id-types/</id>
		<content type="html">&lt;p&gt;I previously wrote about &lt;a href=&quot;https:&#x2F;&#x2F;bytes.zone&#x2F;posts&#x2F;custom-id-types&#x2F;&quot;&gt;tradeoffs of custom ID types in Elm&lt;&#x2F;a&gt;, and promised to explore a few points in the space.
This is one!&lt;&#x2F;p&gt;
&lt;p&gt;Recently I built a little game for my son to learn his letters and decided to see if I could find a nicer way to solve id-as-string problem.
My goal was to find something in between the wild wild west of using &lt;code&gt;String&lt;&#x2F;code&gt;s directly and the repetition of defining ID types for every resource.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I found something new to me.
Maybe it&#x27;ll be useful to you, too!
The gist: &lt;strong&gt;you can make a reusable ID type by using phantom types&lt;&#x2F;strong&gt; (that is, types with a variable that appears in the definition but not any of the constructors.)&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s define a module for our IDs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;elm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Id&lt;&#x2F;span&gt;&lt;span&gt;(..),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; decoder&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; sorter&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Decoder&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Sort&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Sorter&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;decoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Decoder&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; thing&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;decoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Id Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;sorter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Sorter&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; thing&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;sorter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Sort&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;by (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Id&lt;&#x2F;span&gt;&lt;span&gt; id)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; id)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Sort&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;alphabetical&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You use it in external definitions like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;elm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Shelter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Shelter&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Cat&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Decoder&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type alias Shelter =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; adoptableCats&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : List&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Id Cat&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;decoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Decoder Shelter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;decoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;map2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Shelter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;string)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;adoptableCats&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;decoder))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Of course, this improvement still doesn&#x27;t come for free.
&lt;code&gt;Id thing&lt;&#x2F;code&gt; is still not &lt;code&gt;comparable&lt;&#x2F;code&gt; so you need a workaround if you want to use it in a &lt;code&gt;Dict&lt;&#x2F;code&gt; or &lt;code&gt;Set&lt;&#x2F;code&gt; (&lt;code&gt;sorter&lt;&#x2F;code&gt; above.)&lt;&#x2F;p&gt;
&lt;p&gt;You also lose some of the assurance that you&#x27;re not constructing or matching on &lt;code&gt;Id&lt;&#x2F;code&gt; in places where you shouldn&#x27;t be.
To put it another way, you can make a bad ID pretty easily: just call &lt;code&gt;Id &quot;a hot dog is a salad&quot;&lt;&#x2F;code&gt;.
Because the type is not used in the constructor, that&#x27;s a valid &lt;em&gt;whatever&lt;&#x2F;em&gt;... &lt;code&gt;Id Cat&lt;&#x2F;code&gt;, &lt;code&gt;Id Dog&lt;&#x2F;code&gt;, it&#x27;s all the same to the constructor.&lt;&#x2F;p&gt;
&lt;p&gt;You also can&#x27;t embed the ID of a record in the record itself (doing so would be a recursive definition.)
I managed to get around needing this in my alphabet game (records do not need to refer to themselves because whenever they&#x27;re displayed there&#x27;s another piece of data containing the ID.)
If you need it, you just do a little type trick:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;elm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; decoder&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Decoder&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;{-| An module-internal identifier. Has to be a custom type instead of an&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;alias so the compiler can detect if it&amp;#39;s being used improperly.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;-}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type CatId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; CatId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type alias Id =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.Id CatId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type alias Cat =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; purriness&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;decoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Decoder Cat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;decoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;map3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;id&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;decoder)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;string)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;purriness&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Decode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;int)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All the instances of &lt;code&gt;CatId&lt;&#x2F;code&gt; should be erased during compilation.
That is, they shouldn&#x27;t end up adding any weight to your compiled JavaScript!&lt;&#x2F;p&gt;
&lt;p&gt;And there you have it!
To sum up: with this technique, you get the normal stuff you get by using custom types as IDs, like:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;nice type-checking and good errors (with really obvious fixes, like &quot;you have a &lt;code&gt;Id Dog&lt;&#x2F;code&gt; but you need an &lt;code&gt;Id Cat&lt;&#x2F;code&gt;.&quot;)&lt;&#x2F;li&gt;
&lt;li&gt;reasonable levels of code reuse&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;But with these tradeoffs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You have to deal with &lt;code&gt;comparable&lt;&#x2F;code&gt; not being extendable by custom types (which is true without a phantom type, too.)&lt;&#x2F;li&gt;
&lt;li&gt;You have to be disciplined about not deconstructing&#x2F;matching against IDs in places where it wouldn&#x27;t make sense to take responsibility for constructing them.&lt;&#x2F;li&gt;
&lt;li&gt;To get IDs in the records themselves (as opposed to just in &lt;code&gt;Dict&lt;&#x2F;code&gt;s or whatever) you have to do a type trick.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So would I do this again?
I think I would avoid using this in a project where I didn&#x27;t have a high confidence that my fellow programmers knew the intent of the module.
That means that I wouldn&#x27;t to publish a package using this, or contribute code to a high-traffic open source repo using this pattern.
But on my work team in private code, or in small apps that I build for myself, I&#x27;ll definitely be coming back to this!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Bang Shortcuts</title>
		<published>2020-12-08T00:00:00+00:00</published>
		<updated>2020-12-08T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/bang-shortcuts/" type="text/html"/>
		<id>https://bytes.zone/posts/bang-shortcuts/</id>
		<content type="html">&lt;p&gt;Another you-get-to-join-the-&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;1053&#x2F;&quot;&gt;lucky-ten-thousand&lt;&#x2F;a&gt; post here!&lt;&#x2F;p&gt;
&lt;p&gt;There are a bunch of shortcuts involving exclamation marks that shells will automatically expand.
There are so many things you can do with these!
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;linux.die.net&#x2F;man&#x2F;1&#x2F;bash&quot;&gt;See the &quot;History Expansion&quot; section of the Bash manual page&lt;&#x2F;a&gt;)
I just want to highlight a few of the ones I use every day:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;!!&lt;&#x2F;code&gt; expands to the same command you just typed.
For example, entering &lt;code&gt;!!&lt;&#x2F;code&gt; will just re-run your last command.
You can use this to fix goof-ups.
For example, &lt;code&gt;sudo !!&lt;&#x2F;code&gt; will re-run the last command with &lt;code&gt;sudo&lt;&#x2F;code&gt; prepended.
Perfect for when you &lt;code&gt;apt-get install&lt;&#x2F;code&gt; something as your login user instead of &lt;code&gt;root&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;!$&lt;&#x2F;code&gt; expands to the last argument of the last command
I mostly use this to perform several operations on the same file.
For example, I &lt;code&gt;mv config&#x2F;database.yml.sample config&#x2F;database.yml&lt;&#x2F;code&gt;, then &lt;code&gt;kak !$&lt;&#x2F;code&gt; to edit it to customize.
I also use this frequently to preview something that I&#x27;ve just generated.
For example, &lt;code&gt;make dist&#x2F;index.json&lt;&#x2F;code&gt; and then &lt;code&gt;less !$&lt;&#x2F;code&gt; (or &lt;code&gt;jq . !$&lt;&#x2F;code&gt; to pretty-print JSON!)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;!*&lt;&#x2F;code&gt; expands to all the arguments of the last command
I don&#x27;t use this one as much, but it&#x27;s helpful when I misspell a command.
For example, I use a tool called &lt;code&gt;aide&lt;&#x2F;code&gt; at work.
I often misspell it (e.g. &lt;code&gt;aid build monolith&lt;&#x2F;code&gt;), say &quot;ah shoot, again?&quot; and then correct with &lt;code&gt;aide !*&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Some shells (ZSH at least) will automatically expand these for you if you hit tab after typing them so you can see what you&#x27;re in for before you commit.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>the hyper key</title>
		<published>2020-12-02T00:00:00+00:00</published>
		<updated>2020-12-02T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/hyper-key/" type="text/html"/>
		<id>https://bytes.zone/posts/hyper-key/</id>
		<content type="html">&lt;p&gt;Have you ever noticed that no app binds a default keyboard shortcut to shift+control+option+command+whatever?
That means you have a potential global keybinding prefix that will never have any conflicts!
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;brettterpstra.com&#x2F;2012&#x2F;12&#x2F;08&#x2F;a-useful-caps-lock-key&#x2F;&quot;&gt;Brett Terpstra calls this the hyper key&lt;&#x2F;a&gt;.
I like that!&lt;&#x2F;p&gt;
&lt;p&gt;Since this is hard to hit (which, well, that&#x27;s why nobody uses it by default) I have this bound to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;BrianHicks&#x2F;26b5c7739d909061f91432bd78897218&quot;&gt;a special key on my Kinesis&lt;&#x2F;a&gt;, but I find it acceptable to use on my normal laptop QWERTY keyboard too.
Here are some goodies I get:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;hyper + arrow: move a window to the top&#x2F;right&#x2F;bottom&#x2F;left half of my screen with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mizage.com&#x2F;divvy&#x2F;&quot;&gt;Divvy&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;hyper + space: open &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;culturedcode.com&#x2F;things&#x2F;&quot;&gt;Things&lt;&#x2F;a&gt;&#x27; quick input&lt;&#x2F;li&gt;
&lt;li&gt;hyper + semicolon: open Things&#x27; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;culturedcode.com&#x2F;things&#x2F;mac&#x2F;help&#x2F;things-sandboxing-helper-things3&#x2F;&quot;&gt;quick input with autofill&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;hyper + j: open the link for my next meeting with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;apps.apple.com&#x2F;us&#x2F;app&#x2F;next-meeting&#x2F;id1017470484?mt=12&quot;&gt;Next Meeting&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;hyper + enter: jump to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kapeli.com&#x2F;dash&quot;&gt;Dash&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;hyper + &#x27;: jump to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;iterm2.com&quot;&gt;iTerm2&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are a few more I have bound (fullscreen, middle-of-screen shortcuts) but those are the main ones.
I&#x27;ve found them really handy over the years.
Let me know if you have any really good ones for yourself!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>elo-anything</title>
		<published>2020-11-24T00:00:00+00:00</published>
		<updated>2020-11-24T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/elo-anything/" type="text/html"/>
		<id>https://bytes.zone/posts/elo-anything/</id>
		<content type="html">&lt;p&gt;Part of my job as a team lead is dealing with a big list of work that my team is responsible for, but which we&#x27;re not going to do immediately.
Hypothetically, this is work we should be doing all the time, but we&#x27;re a small team with a lot of responsibility, so our list tends to grow.
We &lt;em&gt;should&lt;&#x2F;em&gt; work on the most impactful tasks first... but in a list of 40 or 50 little things, it&#x27;s hard to know what&#x27;s most important.
This makes the job much harder!&lt;&#x2F;p&gt;
&lt;p&gt;After a recent quarter where we didn&#x27;t accomplish any ownership work at all (screaming face emoji), something had to change.
So I built a tool to help!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Before we continue, I&#x27;m assuming it&#x27;s possible to get time allocated to maintenance&#x2F;ownership work.
I&#x27;ve heard of (and been on) engineering teams that can&#x27;t get this time, but that&#x27;s not a problem my current team has.
I have a good enough relationship with our stakeholders that they trust me if I say something needs to be worked on.
Plus, we&#x27;ve baked it into our schedules: at minimum, the team spends 15% of our time on maintenance&#x2F;ownership work in any given week.
(I save my Fridays for this.
It&#x27;s nice to end the week by hammering out 4 or 5 quick bug fixes!)&lt;&#x2F;p&gt;
&lt;p&gt;But even with the time allocated, we can&#x27;t make progress if we don&#x27;t know what to work on, so I set out to figure out how to select tasks from our big list.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;so-what-do-we-work-on&quot;&gt;So what do we work on?&lt;&#x2F;h2&gt;
&lt;p&gt;To start, whatever selection method needs to give me a rough order pretty quickly.
I can&#x27;t choose the most important thing among 50 items; there are just too many things to keep in my head and I&#x27;ll have to guess.
But it &lt;em&gt;is&lt;&#x2F;em&gt; possible if I&#x27;m picking from 10, or better yet 5, so getting even a rough order helps a lot!&lt;&#x2F;p&gt;
&lt;p&gt;Items get added to the list over time, too, so the selection method needs to handle that gracefully.
I don&#x27;t want to make a bajillion comparisons for every new item to figure out exactly where it goes.
And again, a rough order is acceptable: this doesn&#x27;t have to be extremely precise, just good enough to choose which things to work on next.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d also like the order to be able to change over time without having to re-sort the entire list.
If we learn something new or change our minds about where we want to focus, it should be easy to get a new order.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-elo-rating-system&quot;&gt;The Elo rating system&lt;&#x2F;h2&gt;
&lt;p&gt;Fortunately for us, there&#x27;s something that solves for these constraints well already: the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Elo_rating_system&quot;&gt;Elo rating system&lt;&#x2F;a&gt;!
It defines a way to get a rough ranking of a big list as well as a way to adjust the ranks quickly when an item is over- or underrated.
It&#x27;s been used to rank chess players forever, too, so I&#x27;m not starting from scratch on a novel algorithm!&lt;&#x2F;p&gt;
&lt;p&gt;To order a list of items (let&#x27;s call them players to match the original use), you start with everyone at a given rating and start playing matches.
When a player wins, they take a portion of the losing player&#x27;s rating proportional to the difference between the two ratings.
Then to get rankings, you simply sort players from the highest rating to lowest.&lt;&#x2F;p&gt;
&lt;p&gt;The algorithm used to determine the rating change says:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;When two players are evenly matched, the winner&#x27;s rating will go up by a little. There&#x27;s not a lot of useful information in match outcomes when players are evenly ranked: a win could indicate a skill difference, but it could also be a fluke.&lt;&#x2F;li&gt;
&lt;li&gt;If an underrated player wins against someone more highly-ranked, the underrated player&#x27;s rating will go up a lot. A win here has more information: the winner could be underrated or the loser could be overrated. A fluke could still be possible, but adjustments like these will stabilize rankings over time.&lt;&#x2F;li&gt;
&lt;li&gt;On the other hand, if a strong player wins against someone much lower-ranked, their score doesn&#x27;t rise as much. It shouldn&#x27;t be worth a lot of points to win against players you&#x27;re expected to beat every time.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There&#x27;s a little more to the math to adjust for scenarios where there are few matches played versus many, but that&#x27;s the basic idea.&lt;&#x2F;p&gt;
&lt;p&gt;These rules mean that if you win consistently you&#x27;ll go up in ranking (or if you lose, you&#x27;ll go down.)
Given enough matches, you tend to get a stable ordering of players according to their level!&lt;&#x2F;p&gt;
&lt;p&gt;So does the Elo rating system fit my criteria?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A new or under-rated player can rise in the ranks quickly by challenging random higher-ranked players.&lt;&#x2F;li&gt;
&lt;li&gt;The more comparisons we make in the list, the closer to the true order items will get.&lt;&#x2F;li&gt;
&lt;li&gt;If we change our minds about something&#x27;s importance, the rating can change relatively quickly.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Sounds like a plan!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ok-let-s-use-it&quot;&gt;Ok! Let&#x27;s use it!&lt;&#x2F;h2&gt;
&lt;p&gt;To make a long story short, I built an Elm app to create and maintain these rankings for me, and you can use it right now at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elo.bytes.zone&quot;&gt;elo.bytes.zone&lt;&#x2F;a&gt; (or get the source at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;elo-anything&quot;&gt;git.bytes.zone&#x2F;brian&#x2F;elo-anything&lt;&#x2F;a&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;Some features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You can rank however many items you need, and add and remove them at any time.&lt;&#x2F;li&gt;
&lt;li&gt;There&#x27;s an undo button (which is helpful for second-guessing!)&lt;&#x2F;li&gt;
&lt;li&gt;New items are treated specially. Ideally, they&#x27;ll get to the right ranking in 5 matches or less, and the system favors selecting new items so you can finish those play-in matches quickly.&lt;&#x2F;li&gt;
&lt;li&gt;You can load and save rankings to persist items over time.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ve been super happy to use this to sort our task list, and now our ownership work is consistently being accomplished!
A nice benefit that I hadn&#x27;t anticipated: when I go through the list with my team&#x27;s PM, we can have a productive conversation about the relative importance of &lt;em&gt;this&lt;&#x2F;em&gt; versus &lt;em&gt;that&lt;&#x2F;em&gt;, which has revealed new information about differences in our priorities!
Using this rating system has lead to a couple of good conversations about what the team &lt;em&gt;should&lt;&#x2F;em&gt; be working on, which I&#x27;ve been really happy about.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, that&#x27;s a wrap.
Let me know if you end up using elo-anything.
It&#x27;s fairly polished at this point but I&#x27;m sure there are still weird edge cases we could find and fix!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Tradeoffs of Custom ID Types in Elm</title>
		<published>2020-10-26T00:00:00+00:00</published>
		<updated>2020-10-26T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/custom-id-types/" type="text/html"/>
		<id>https://bytes.zone/posts/custom-id-types/</id>
		<content type="html">&lt;p&gt;In Elm, we store data in records.
Here&#x27;s a model to define a &lt;code&gt;Cat&lt;&#x2F;code&gt;.
Mew!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;elm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Cat&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type alias Cat =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; purriness&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This particular implementation has a problem: you can do &lt;em&gt;whatever&lt;&#x2F;em&gt; with &lt;code&gt;id&lt;&#x2F;code&gt; since it&#x27;s a &lt;code&gt;String&lt;&#x2F;code&gt;.
Wanna concatenate it with another one?
Go right ahead.
Regex match?
Why not!
Using a &lt;code&gt;String&lt;&#x2F;code&gt; here means that the compiler can&#x27;t tell you if you&#x27;re using the &lt;code&gt;id&lt;&#x2F;code&gt; in the way it was intended to be used.&lt;&#x2F;p&gt;
&lt;p&gt;One name for this is &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;wiki.c2.com&#x2F;?PrimitiveObsession&quot;&gt;primitive obsession&lt;&#x2F;a&gt;, meaning code uses a language primitive (&lt;code&gt;String&lt;&#x2F;code&gt; here) instead of a domain-specific object.
This can make it harder to reason about and refactor the code, not to mention the fact that it doesn&#x27;t guard against the problems above.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;a-custom-type&quot;&gt;A Custom Type&lt;&#x2F;h2&gt;
&lt;p&gt;We can fix this in Elm by making a custom ID type:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;elm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; exposing&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;Cat&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; CatId&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type CatId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; CatId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;type alias Cat =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : CatId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; purriness&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With this, you get pretty much all the benefits of wrapping a primitive in a class in other languages:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;we can distinguish between a &lt;code&gt;CatId&lt;&#x2F;code&gt; and a (hypothetical) &lt;code&gt;DogId&lt;&#x2F;code&gt;. Before defining a type, these would have both been &lt;code&gt;String&lt;&#x2F;code&gt;s and totally indistinguishable.&lt;&#x2F;li&gt;
&lt;li&gt;We&#x27;ve disallowed &lt;code&gt;String&lt;&#x2F;code&gt; operations on the type. There will be no more concatenation, regex matching, et cetera.&lt;&#x2F;li&gt;
&lt;li&gt;If we don&#x27;t export the &lt;code&gt;CatId&lt;&#x2F;code&gt; constructor, nobody outside the &lt;code&gt;Cat&lt;&#x2F;code&gt; module can construct a value of &lt;code&gt;CatId&lt;&#x2F;code&gt;. With this setup, we can be pretty safe trusting values of &lt;code&gt;CatId&lt;&#x2F;code&gt; have been constructed in safe ways (for example, that they&#x27;ve been deserialized from JSON provided by the server.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The benefits have been well-documented elsewhere and are (in my opinion) pretty unambiguous, so I&#x27;m keeping this brief.
I want to spend more time talking about the drawbacks of doing it in this particular way and how to get around them!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-is-harder&quot;&gt;Testing is Harder&lt;&#x2F;h2&gt;
&lt;p&gt;First of all, we can no longer construct values of &lt;code&gt;CatId&lt;&#x2F;code&gt; in tests.
This means we cannot construct any records or structures that depend on &lt;code&gt;CatId&lt;&#x2F;code&gt; either.
No &lt;code&gt;Cat&lt;&#x2F;code&gt;, no &lt;code&gt;Model&lt;&#x2F;code&gt; containing &lt;code&gt;Cat&lt;&#x2F;code&gt;s, etc.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve seen some test code provide hand-rolled JSON values to a &lt;code&gt;Decoder Cat&lt;&#x2F;code&gt; to get around this, but I&#x27;m not sure that&#x27;s such a good idea.
It raises complexity in tests and ties them to the &lt;code&gt;decoder&lt;&#x2F;code&gt; instead of only the implementation we&#x27;re trying to verify.&lt;&#x2F;p&gt;
&lt;p&gt;Instead, I usually end up writing some constructor function like &lt;code&gt;catIdForTestOnly : String -&amp;gt; CatId&lt;&#x2F;code&gt;.
It has the same effect as exposing the constructor, but labels your intent clearly.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d call this pragmatic, but I know there are reasonable people who&#x27;d call it a code smell.
That&#x27;s fine!
We can disagree!
But, regardless of your approach, you&#x27;ll have to deal with the tradeoff of the hidden constructor here.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;no-comparable-implementation&quot;&gt;No &lt;code&gt;comparable&lt;&#x2F;code&gt; Implementation&lt;&#x2F;h2&gt;
&lt;p&gt;Second, you can&#x27;t use &lt;code&gt;CatId&lt;&#x2F;code&gt; where the compiler expects something matching &lt;code&gt;comparable&lt;&#x2F;code&gt;.
This shows up pretty frequently because &lt;code&gt;elm&#x2F;core&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;Dict&lt;&#x2F;code&gt; needs keys to be &lt;code&gt;comparable&lt;&#x2F;code&gt; in order to provide fast lookups.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately for us, the Elm community know about this (hopefully temporary) problem and has published a bunch of different packages to help get around it.
I like &lt;code&gt;rtfeldman&#x2F;elm-sorter-experiment&lt;&#x2F;code&gt;, in which we just need to define a custom sorter function and pass it to dictionary and set constructors.
For &lt;code&gt;CatId&lt;&#x2F;code&gt;, that looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;elm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;idSorter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; : Sorter CatId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;idSorter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Sort&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;by (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;CatId&lt;&#x2F;span&gt;&lt;span&gt; id)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; id)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; Sort&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;alphabetical&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can construct dictionaries and sets containing &lt;code&gt;CatId&lt;&#x2F;code&gt; for fast lookups and still get the benefits we want.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;&#x2F;h2&gt;
&lt;p&gt;So, that&#x27;s one way to make sure you&#x27;re doing the right things with IDs (or other small data) in your Elm code.&lt;&#x2F;p&gt;
&lt;p&gt;Should you do it?
I think that despite the tradeoffs, it&#x27;s a &lt;strong&gt;yes&lt;&#x2F;strong&gt; most of the time!
I&#x27;ve had a lot of success with doing this, and I&#x27;d recommend it to others.&lt;&#x2F;p&gt;
&lt;p&gt;That said, there are some more ways to solve this problem, and I&#x27;m planning on exploring them in future posts.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Renaming Files with Braces</title>
		<published>2020-10-19T00:00:00+00:00</published>
		<updated>2020-10-19T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/renaming-files/" type="text/html"/>
		<id>https://bytes.zone/posts/renaming-files/</id>
		<content type="html">&lt;p&gt;This has been well-documented elsewhere, but in the interests of helping someone else join &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;1053&#x2F;&quot;&gt;the lucky ten thousand&lt;&#x2F;a&gt; today:
Bash, ZSH, and other shells will expand comma-sepated arguments surrounded by braces into positional arguments.&lt;&#x2F;p&gt;
&lt;p&gt;As an altogether contrived example, imagine you want to echo &quot;cat car can&quot; in the terminal.
You could write &lt;code&gt;echo cat car can&lt;&#x2F;code&gt;, right?
But then you&#x27;d be writing &lt;code&gt;ca&lt;&#x2F;code&gt; twice.
That won&#x27;t do!
We must be &lt;strong&gt;maximally efficient&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;We can use brace expansion to only write &lt;code&gt;ca&lt;&#x2F;code&gt; once: &lt;code&gt;echo ca{t,r,n}&lt;&#x2F;code&gt;.
The shell will expand that to your original &quot;cat car can.&quot;
Ta-da!&lt;&#x2F;p&gt;
&lt;p&gt;Next, say you want to add &quot;bat bar ban&quot; to the output.
Good news: the shell will expand more than one set of braces in the same word!
&lt;code&gt;echo {c,b}a{t,r,n}&lt;&#x2F;code&gt; will get you &quot;cat car can bat bar ban&quot;.
Sweet!&lt;&#x2F;p&gt;
&lt;p&gt;So, back to real life... when is this actually useful?
Well, I use it all the time for renaming or moving files!&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mv someFile.txt{,.bak}&lt;&#x2F;code&gt; expands to &lt;code&gt;mv someFile.txt someFile.txt.bak&lt;&#x2F;code&gt;, adding the &lt;code&gt;.bak&lt;&#x2F;code&gt; extension&lt;&#x2F;li&gt;
&lt;li&gt;conversely, &lt;code&gt;mv someFile.txt{.bak,}&lt;&#x2F;code&gt; removes the &lt;code&gt;.bak&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;mv config&#x2F;database.yml{.sample,}&lt;&#x2F;code&gt; expands to &lt;code&gt;mv config&#x2F;database.yml.sample config&#x2F;database.yml&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cp {src,dist}&#x2F;index.js&lt;&#x2F;code&gt; works great in &lt;code&gt;Makefile&lt;&#x2F;code&gt;s too!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Some shells (ZSH at least) will even expand that if you hit &lt;code&gt;tab&lt;&#x2F;code&gt; so you can see what you&#x27;re going to execute before you hit commit.
Handy!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>state-transition tables</title>
		<published>2020-10-13T00:00:00+00:00</published>
		<updated>2020-10-13T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/state-transition-tables/" type="text/html"/>
		<id>https://bytes.zone/posts/state-transition-tables/</id>
		<content type="html">&lt;p&gt;I was reading Wikipedia the other day (as you do) and found out about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;State-transition_table&quot;&gt;state-transition tables&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Basically, state transition tables show how a state machine transitions between different states.
It&#x27;s an alternative to drawing a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;State_diagram&quot;&gt;state diagram&lt;&#x2F;a&gt; that helps you find holes in your logic.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Wikipedia shows some pretty abstract tables, so I&#x27;m going to model a vending machine instead.
To simplify things, we&#x27;ll serve a single drink for a single quarter.
The idealized version of the interaction with this machine (the &quot;happy path&quot;) is:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Put a quarter in&lt;&#x2F;li&gt;
&lt;li&gt;Press the button for the drink you want&lt;&#x2F;li&gt;
&lt;li&gt;Get the drink&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;To implement this, we have to manage two independent pieces of state: whether you&#x27;ve put money in the machine and whether it has at least one drink left to vend.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s model the interaction above with a one-dimensional state-transition table.
Using only one dimension keeps the modeling as simple as possible while still capturing enough detail to be useful: we have a column each for &lt;strong&gt;input&lt;&#x2F;strong&gt;, &lt;strong&gt;current state&lt;&#x2F;strong&gt;, &lt;strong&gt;next state&lt;&#x2F;strong&gt;, and &lt;strong&gt;side effects&lt;&#x2F;strong&gt;.
To find out what happens after an event you just find the &lt;strong&gt;input&lt;&#x2F;strong&gt; and &lt;strong&gt;current state&lt;&#x2F;strong&gt; rows you care about and look at the matching &lt;strong&gt;next state&lt;&#x2F;strong&gt; and &lt;strong&gt;side effect&lt;&#x2F;strong&gt;.
For our vending machine, it might look like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Input&lt;&#x2F;th&gt;&lt;th&gt;Current State&lt;&#x2F;th&gt;&lt;th&gt;Next State&lt;&#x2F;th&gt;&lt;th&gt;Side Effect&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Insert Quarter&lt;&#x2F;td&gt;&lt;td&gt;No Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;Some Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;-&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Hit Button&lt;&#x2F;td&gt;&lt;td&gt;Some Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;No Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;Vend Drink&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;But, of course, we have to model what happens when we do things that are not on the happy path.
Unfortunately, the one-dimensional version of the table doesn&#x27;t give us a great view of that!&lt;&#x2F;p&gt;
&lt;p&gt;To figure out where we have holes, we need to add more dimensions.
Let&#x27;s reorganize our states along the vertical axis and inputs along the horizontal axis to get a two-dimensional state-transition table.&lt;&#x2F;p&gt;
&lt;p&gt;To read this table, match the &lt;strong&gt;current state&lt;&#x2F;strong&gt; along the vertical axis with the &lt;strong&gt;input&lt;&#x2F;strong&gt; along the horizontal.
Our &lt;strong&gt;next state&lt;&#x2F;strong&gt; and &lt;strong&gt;side effects&lt;&#x2F;strong&gt; live in the intersections (I&#x27;ve separated them with a &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;↓ Current State &#x2F; Input →&lt;&#x2F;th&gt;&lt;th&gt;Insert Quarter&lt;&#x2F;th&gt;&lt;th&gt;Hit Button&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;No Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;Some Money, Some Drinks &#x2F; Nothing&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Some Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;No Money, Some Drinks &#x2F; Vend Drink&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;No Money, No Drinks&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Some Money, No Drinks&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;And we see, uh... problems.
When we look at things this way, it&#x27;s clear that we&#x27;ve only defined two of the possible 8 outcomes!
Writing things down in an orderly way revealed that we haven&#x27;t specified all of the possibilities implied by our modeling.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s fill the rest out.
To make things easier, when the state stays the same or there&#x27;s no side effect I&#x27;ve marked &lt;code&gt;-&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;↓ Current State &#x2F; Input →&lt;&#x2F;th&gt;&lt;th&gt;Insert Quarter&lt;&#x2F;th&gt;&lt;th&gt;Hit Button&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;No Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;Some Money, Some Drinks &#x2F; -&lt;&#x2F;td&gt;&lt;td&gt;- &#x2F; Beep&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Some Money, Some Drinks&lt;&#x2F;td&gt;&lt;td&gt;- &#x2F; Refund Quarter&lt;&#x2F;td&gt;&lt;td&gt;No Money, Some Drinks &#x2F; Vend Drink&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;No Money, No Drinks&lt;&#x2F;td&gt;&lt;td&gt;- &#x2F; Refund Quarter&lt;&#x2F;td&gt;&lt;td&gt;- &#x2F; Beep&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Some Money, No Drinks&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;???&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;???&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;But when we fill things out, we can see that we have a potentially weird situation: what if we somehow have some money, but no drinks?
The state machine should prevent that, since there&#x27;s no new state field that could create this situation.
But it&#x27;s feasible to get there either via programming (for example, by modeling the state as two independent fields) or hardware issues (for example, someone prying open the machine to leave quarters in an atypical act of vandalism.)&lt;&#x2F;p&gt;
&lt;p&gt;Our modeling has revealed this undefined behavior way before we got to the code parts of our application, and the hardest part was making a table and looking for empty cells.
Now I can take this same table to a stakeholder or domain expert and have a productive conversation about what they think should happen.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d call that a win for just a little time spent modeling!&lt;&#x2F;p&gt;
&lt;p&gt;(oh, and bonus: if you&#x27;re using Elm, the one-dimensional form here is probably pretty familiar.
&quot;Input, Current State, Next State, Output&quot; does the same job as &lt;code&gt;update : msg -&amp;gt; model -&amp;gt; ( model, Cmd msg )&lt;&#x2F;code&gt;!)&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Thanks to Charlie Koster and Richard Feldman for reviewing drafts of this post.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>clown computing</title>
		<published>2020-10-12T00:00:00+00:00</published>
		<updated>2020-10-12T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/clown-computing/" type="text/html"/>
		<id>https://bytes.zone/posts/clown-computing/</id>
		<content type="html">&lt;p&gt;I keep using the term &quot;clown computing&quot; in conversations but I&#x27;ve never defined it.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Clown computing is a riff on &quot;cloud computing&quot;, but instead of provisioning your software according to high availability or other actual reasonable architectural principles, you just stuff as much software as you can into a single cloud VM.&lt;&#x2F;p&gt;
&lt;p&gt;Like a clown car.
Get it?&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Fuzzy Finding with Levenstein Distance</title>
		<published>2020-10-06T00:00:00+00:00</published>
		<updated>2020-10-06T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/similar-sort/" type="text/html"/>
		<id>https://bytes.zone/posts/similar-sort/</id>
		<content type="html">&lt;p&gt;When I&#x27;m navigating around a codebase I know, I prefer to use a fuzzy file finder instead of browsing a directory tree.
Lowering the barrier to navigating between files is really helpful for me to move around a codebase quickly and get work done.
Ideally, I type a few characters and end up in the file that I was thinking of, quick as that.&lt;&#x2F;p&gt;
&lt;p&gt;But in late 2019, I got fed up trying to fuzzy-find files in my main repo at work.
It has something like 8,000 checked-in files and 170,000 untracked files (bundled gems, &lt;code&gt;node_modules&lt;&#x2F;code&gt;, etc.) so it took a long time to load all the files.
To add to that, &lt;code&gt;fzf&lt;&#x2F;code&gt;&#x27;s default configuration favors short matches near the beginning of the string.
When I&#x27;m deep in the project hierarchy, I want long matches with my term near the end first!&lt;&#x2F;p&gt;
&lt;p&gt;In short: my fuzzy matches became both &lt;em&gt;slow&lt;&#x2F;em&gt; and &lt;em&gt;irrelevant&lt;&#x2F;em&gt;.
Not a good combination for a tool meant to save me time!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Both of these things are fairly easy fixes on their own (I could sub in &lt;code&gt;git ls-files&lt;&#x2F;code&gt; for &lt;code&gt;find&lt;&#x2F;code&gt; to get matches and configure fzf to sort by long&#x2F;end instead of short&#x2F;beginning.)
But I decided that I wanted to improve my experience: in addition to end-of-filename matching, I want to favor matches for equivalent test files.
If I&#x27;m in a file and I type &quot;spec&quot; or &quot;test&quot; (depending on the language conventions) I should ideally get to the relevant test code.&lt;&#x2F;p&gt;
&lt;p&gt;Given that most test files have some name symmetry to the files they test (e.g. &lt;code&gt;app&#x2F;models&#x2F;user.rb&lt;&#x2F;code&gt;, &lt;code&gt;spec&#x2F;models&#x2F;user_spec.rb&lt;&#x2F;code&gt;), what I want is files that are named similarly to the file I&#x27;m currently editing.
Enter Levenshtein distance, an algorithm which calculates the amount of edits you&#x27;d have to change to change a string into another string.
For example:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a&lt;&#x2F;code&gt; to &lt;code&gt;ab&lt;&#x2F;code&gt; has an edit distance of 1, because you add &lt;code&gt;b&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ab&lt;&#x2F;code&gt; to &lt;code&gt;a&lt;&#x2F;code&gt; also has an edit distance of 1, because you remove &lt;code&gt;b&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;a&lt;&#x2F;code&gt; to &lt;code&gt;b&lt;&#x2F;code&gt;, on the other hand, has an edit distance of... 1! The first time I looked at this, I thought it was 2 (add 1, remove 1) but replacements also count as a single operation!&lt;&#x2F;li&gt;
&lt;li&gt;ok, more complex: &lt;code&gt;grammar&lt;&#x2F;code&gt; to &lt;code&gt;programmer&lt;&#x2F;code&gt; is 4. (add &lt;code&gt;pro&lt;&#x2F;code&gt;, replace &lt;code&gt;a&lt;&#x2F;code&gt; with &lt;code&gt;e&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can do this with any two strings, although it requires more calculations as the strings get longer.
Let&#x27;s apply this to filenames!
Starting from &lt;code&gt;app&#x2F;models&#x2F;user.rb&lt;&#x2F;code&gt;, &lt;code&gt;spec&#x2F;models&#x2F;user_spec.rb&lt;&#x2F;code&gt; has an edit distance of 8, but a less similarly-named file, &lt;code&gt;app&#x2F;controllers&#x2F;admin_controller.rb&lt;&#x2F;code&gt;, has an edit distance of 22.&lt;&#x2F;p&gt;
&lt;p&gt;If we sort our candidate files by their edit distance from the source string, we can tell &lt;code&gt;fzf&lt;&#x2F;code&gt; that the input files are already in preference order (&lt;code&gt;--tiebreak=index&lt;&#x2F;code&gt;) and get exactly the editing experience I was after!&lt;&#x2F;p&gt;
&lt;p&gt;So, does this work?
I&#x27;m happy to report that it totally does!
I&#x27;ve been using similar-sort for about a year and a half now with no modifications, and I anticipate being able to use it as long as I need!&lt;&#x2F;p&gt;
&lt;p&gt;And now you can grab it as well at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;similar-sort&quot;&gt;git.bytes.zone&#x2F;brian&#x2F;similar-sort&lt;&#x2F;a&gt;.
That repo includes instructions for building and installing, as well as integration with Kakoune and Vim.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s licensed CC BY-SA 4.0 (since that&#x27;s the license for the implementation of the Levenshtein distance implementation I grabbed from Wikipedia.)&lt;&#x2F;p&gt;
&lt;p&gt;Enjoy, and let me know if you use it!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>callCabal2nix</title>
		<published>2020-09-22T00:00:00+00:00</published>
		<updated>2020-09-22T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/callcabal2nix/" type="text/html"/>
		<id>https://bytes.zone/posts/callcabal2nix/</id>
		<content type="html">&lt;p&gt;When you&#x27;re setting up a Haskell project in Nix, most advice says &quot;call &lt;code&gt;cabal init&lt;&#x2F;code&gt; and then &lt;code&gt;cabal2nix . &amp;gt; default.nix&lt;&#x2F;code&gt; to get a buildable Haskell project.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;But it turns out you don&#x27;t have to run &lt;code&gt;cabal2nix&lt;&#x2F;code&gt; by hand to keep it up to date!
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NixOS&#x2F;nixpkgs&#x2F;blob&#x2F;34f475f5eae13d18b4e4b8a17aa7a772d8619b0b&#x2F;pkgs&#x2F;development&#x2F;haskell-modules&#x2F;make-package-set.nix#L216&quot;&gt;&lt;code&gt;pkgs.haskellPackages.callCabal2nix&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; does the same thing as &lt;code&gt;cabal2nix&lt;&#x2F;code&gt;, but without writing a file.
The nixpkgs manual doesn&#x27;t mention it since it uses import-from-derivation, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NixOS&#x2F;nixpkgs&#x2F;issues&#x2F;16130#issuecomment-229939552&quot;&gt;which can cause some problems with Hydra&lt;&#x2F;a&gt; but I&#x27;ve found it super helpful so I&#x27;m writing it up!
If you need more detail than what I&#x27;ve written here, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NixOS&#x2F;nixpkgs&#x2F;blob&#x2F;34f475f5eae13d18b4e4b8a17aa7a772d8619b0b&#x2F;pkgs&#x2F;development&#x2F;haskell-modules&#x2F;make-package-set.nix#L216&quot;&gt;diving into the source is probably your best bet&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;The basic usage: call &lt;code&gt;pkgs.haskellPackages.callCabal2nix&lt;&#x2F;code&gt; (note the lowercase &lt;code&gt;n&lt;&#x2F;code&gt; in &lt;code&gt;nix&lt;&#x2F;code&gt;) with...&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;the project name&lt;&#x2F;li&gt;
&lt;li&gt;the path to the project source&lt;&#x2F;li&gt;
&lt;li&gt;the set of options you&#x27;d normally provide to a call to the stuff &lt;code&gt;cabal2nix&lt;&#x2F;code&gt; generates&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If you want something similar to the &lt;code&gt;default.nix&lt;&#x2F;code&gt; produced by the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nixos.org&#x2F;manual&#x2F;nixpkgs&#x2F;stable&#x2F;#haskell&quot;&gt;&lt;code&gt;cabal2nix&lt;&#x2F;code&gt; instructions in the nixpkgs manual&lt;&#x2F;a&gt;, put this in &lt;code&gt;default.nix&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{ pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;lt;nixpkgs&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; { }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;, ...&lt;&#x2F;span&gt;&lt;span&gt; }:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;haskellPackages&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;callCabal2nix&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;name-of-your-haskell-project&amp;quot; .&#x2F;.&lt;&#x2F;span&gt;&lt;span&gt; { }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;callCabal2nix&lt;&#x2F;code&gt; is always a sibling of &lt;code&gt;ghcWithPackages&lt;&#x2F;code&gt;, so you can pin your compiler version in the same way:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{ pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;lt;nixpkgs&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; { }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;, ...&lt;&#x2F;span&gt;&lt;span&gt; }:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;haskell&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;packages&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;ghc865&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;callCabal2nix&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;name-of-your-haskell-project&amp;quot; .&#x2F;.&lt;&#x2F;span&gt;&lt;span&gt; { }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you rely on getting a build environment with &lt;code&gt;cabal2nix&lt;&#x2F;code&gt;, the output &lt;code&gt;callCabal2nix&lt;&#x2F;code&gt; has a &lt;code&gt;.env&lt;&#x2F;code&gt; which you can provide to a &lt;code&gt;mkShell&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;inputsFrom&lt;&#x2F;code&gt; stanza:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{ pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;lt;nixpkgs&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; { }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;, ...&lt;&#x2F;span&gt;&lt;span&gt; }:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;mkShell&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;  inputsFrom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [ (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;haskellPackages&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;callCabal2nix&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;project&amp;quot; .&#x2F;.&lt;&#x2F;span&gt;&lt;span&gt; { })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span&gt; ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you were previously calling &lt;code&gt;cabal2nix&lt;&#x2F;code&gt; with command-line options, you can instead use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NixOS&#x2F;nixpkgs&#x2F;blob&#x2F;34f475f5eae13d18b4e4b8a17aa7a772d8619b0b&#x2F;pkgs&#x2F;development&#x2F;haskell-modules&#x2F;make-package-set.nix#L201-L214&quot;&gt;&lt;code&gt;callCabal2nixWithOptions&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; (again, note the lowercase &lt;code&gt;n&lt;&#x2F;code&gt; in &lt;code&gt;nix&lt;&#x2F;code&gt;.)
This form adds a string before the final argument &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NixOS&#x2F;nixpkgs&#x2F;blob&#x2F;34f475f5eae13d18b4e4b8a17aa7a772d8619b0b&#x2F;pkgs&#x2F;development&#x2F;haskell-modules&#x2F;make-package-set.nix#L136&quot;&gt;which is eventually used literally in a call to &lt;code&gt;cabal2nix&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s it!
Enjoy not having to write &lt;code&gt;make&lt;&#x2F;code&gt; rules to call &lt;code&gt;cabal2nix&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;Oh and one final note: all the code links in this post are links to specific lines the repo as of the time of writing.
If you are trying to find more information in the future, you may want to switch to the &lt;code&gt;master&lt;&#x2F;code&gt; branch of nixpkgs and sniff around for &lt;code&gt;callCabal2nix&lt;&#x2F;code&gt; to get the most up-to-date view.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>git root</title>
		<published>2020-09-10T00:00:00+00:00</published>
		<updated>2020-09-10T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/git-root/" type="text/html"/>
		<id>https://bytes.zone/posts/git-root/</id>
		<content type="html">&lt;p&gt;I sometimes find myself in a situation where I&#x27;ve &lt;code&gt;cd&lt;&#x2F;code&gt;&#x27;d several levels into a project and don&#x27;t remember exactly where I am, but I want to get back to the project root for my next command.
This doesn&#x27;t come up for me &lt;em&gt;alllll&lt;&#x2F;em&gt; the time, but the last time it did I decided to do something about it.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;To cut this short: if you run &lt;code&gt;git rev-parse --show-toplevel&lt;&#x2F;code&gt;, &lt;code&gt;git&lt;&#x2F;code&gt; will print out the location you cloned the repo.
In other words, if you run &lt;code&gt;git clone https:&#x2F;&#x2F;git-host.com&#x2F;user&#x2F;project.git ~&#x2F;code&#x2F;project&lt;&#x2F;code&gt;, that command will return &lt;code&gt;~&#x2F;code&#x2F;project&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;So I&#x27;ve just added that to my aliases: &lt;code&gt;root = &quot;rev-parse --show-toplevel&quot;&lt;&#x2F;code&gt;.
Now I can do &lt;code&gt;git root&lt;&#x2F;code&gt; or &lt;code&gt;cd $(git root)&lt;&#x2F;code&gt;.
That makes this problem much less annoying for me!&lt;&#x2F;p&gt;
&lt;p&gt;Someone pointed out to me that I can probably also add an alias in my shell like &lt;code&gt;alias cdroot=&#x27;cd $(git root)&#x27;&lt;&#x2F;code&gt; to make this even easier, but I haven&#x27;t felt the need for that as urgently yet.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>bad-datalog</title>
		<published>2020-08-06T00:00:00+00:00</published>
		<updated>2022-06-14T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/bad-datalog/" type="text/html"/>
		<id>https://bytes.zone/projects/bad-datalog/</id>
		<content type="html">&lt;p&gt;In 2020, I got really interested in Datalog implementation after reading Pete Vilter&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;petevilter.me&#x2F;post&#x2F;datalog-typechecking&#x2F;&quot;&gt;Codebase as Database: Turning the IDE Inside Out with Datalog&lt;&#x2F;a&gt;.
I did a bunch of reading about different ways to make a Datalog.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.scattered-thoughts.net&#x2F;&quot;&gt;Jamie Brandon&lt;&#x2F;a&gt; advised me to make a miniature relational database and lower (raise?) the datalog layer to it.
That approach got me way further than I had gotten before!
It ended up being released to the public (though not on the Elm package site) as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;brian&#x2F;bad-datalog&quot;&gt;bad-datalog&lt;&#x2F;a&gt;, which you can play with at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;datalog.bytes.zone&#x2F;&quot;&gt;datalog.bytes.zone&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m really glad I did this project; I learned a lot about the things that databases have to do to plan and execute a query, and got some experience with a fun relational language in the bargain.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>tmux-session</title>
		<published>2020-04-16T00:00:00+00:00</published>
		<updated>2020-04-16T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/tmux-session/" type="text/html"/>
		<id>https://bytes.zone/posts/tmux-session/</id>
		<content type="html">&lt;p&gt;I wrote a little script to create and fast-switch between different &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tmux&#x2F;tmux&#x2F;wiki&quot;&gt;tmux&lt;&#x2F;a&gt; sessions, and thought I&#x27;d share it real quick so others can use it too.
It&#x27;s got two composable components you can use in your own stuff:&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;the-script-itself&quot;&gt;The Script Itself&lt;&#x2F;h2&gt;
&lt;p&gt;If I&#x27;m not running the right tmux session, I run &lt;code&gt;tmux-session&lt;&#x2F;code&gt;.
If I&#x27;m not in a tmux client (that is, just in a plain shell) it creates a session with a nice name and opens it.
If such a session already exists, it switches to it instead.
This works inside a client too!
The point is for it to always do the right thing so I don&#x27;t have to think about the four cases: outside and inside a client, with the target session existing or not.&lt;&#x2F;p&gt;
&lt;p&gt;The session names are based on git checkouts, when possible.
If you invoke the script from within a git checkout, it&#x27;ll find the root of the project (the directory containing &lt;code&gt;.git&lt;&#x2F;code&gt;) and name the session after it.
If you&#x27;re outside a git checkout, it&#x27;ll name the session after the current directory.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the commented shell script that you can stick somewhere in your &lt;code&gt;$PATH&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#!&#x2F;usr&#x2F;bin&#x2F;env bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;set -euo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; pipefail&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# find the project root directory by examining each parent starting from the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# current directory. If you use a different VCS, you can change `$ROOT&#x2F;.git`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# below to something else: for example, Mercurial would use `$ROOT&#x2F;.hg`.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;${1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span&gt;pwd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;while !&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; test -d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;.git&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; test&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot; != &amp;quot;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;dirname&lt;&#x2F;span&gt;&lt;span&gt; $ROOT)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# if we walked all the way up and hit the root directory, just name the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# session after the current directory. Handy for creating sessions when&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# you&amp;#39;re working in directories like ~&#x2F;Downloads that won&amp;#39;t be tracked&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# with git.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; test&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot; = &amp;quot;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;pwd&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# tmux doesn&amp;#39;t allow dots in session names, so we replace them with&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# dashes. This shows up surprisingly often in my life! For example, my dotfiles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# repo is named `dotfiles.nix`, which gets normalized to `dotfiles-nix`. This&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# is close enough to the actual name that I know what I&amp;#39;m selecting when I&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# switch to it.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SESSION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;basename&lt;&#x2F;span&gt;&lt;span&gt; $ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; sed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;s&#x2F;\.&#x2F;-&#x2F;g&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# are we in a tmux client already? If we are, `$TMUX` will be set.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; test -z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;${&lt;&#x2F;span&gt;&lt;span&gt;TMUX&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  # ah, we&amp;#39;re not in a client? Create a session and enter it! Some quirk in&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  # my installation make the TMUX_TMPDIR and -u2 necessary when starting&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  # the server; you may not need it.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  exec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; env TMUX_TMPDIR=&#x2F;tmp tmux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -u2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; new-session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -As&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SESSION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  # we&amp;#39;re already in a client? Neat. Let&amp;#39;s make a new session with the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  # target name in the background...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  if !&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; tmux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; has-session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SESSION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    tmux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; new-session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -ds&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SESSION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$ROOT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  # ... and then switch our current client to it!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  exec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; tmux switch-client&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SESSION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;the-quick-jumper&quot;&gt;The Quick Jumper&lt;&#x2F;h2&gt;
&lt;p&gt;I like to be able to jump between project directories quickly by just typing a couple letters of their name.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;junegunn&#x2F;fzf&quot;&gt;&lt;code&gt;fzf&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; helps a lot here!&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the commented ZSH source (but I &lt;em&gt;think&lt;&#x2F;em&gt; this would work in bash as well!)&lt;&#x2F;p&gt;
&lt;p&gt;Note that this assumes my own checkout pattern (I check out all repos like &lt;code&gt;~&#x2F;code&#x2F;owner&#x2F;project&lt;&#x2F;code&gt; and have some git aliases to manage that for me.)
You could just as easily adapt it to your own checkout patterns, or just a commonly used projects list that you keep as a file (just pipe that into fzf!)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;tmux_jump&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # starting in ~&#x2F;code...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    BASE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;code&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # ... look for directories exactly two levels deep (`~&#x2F;code&#x2F;owner&#x2F;project`)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # and match them with fzf. In this case we break ties by favoring matches&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # on the project name instead of the owner name (implementation means&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # favoring matches closer to the end of the string.) This is simplified a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # little bit with the `--select-1 --query=&amp;quot;$1&amp;quot;` line: if there&amp;#39;s only one&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # match for the argument passed in as the first argument to this function,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # we select immediately instead of asking for an interactive selection.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    SELECTED&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;find&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$BASE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -mindepth 2 -maxdepth 2 -type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; sed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;s|&lt;&#x2F;span&gt;&lt;span&gt;$BASE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;||g&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; fzf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --tiebreak=end --select-1 --query=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;$1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # fzf will exit with a non-zero code if you ctrl-c or ctrl-g out of&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # it. We use this as a signal that we don&amp;#39;t want to jump after all.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; [[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;$?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt; ]];&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;cancelling!&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # call tmux-session on the *full* path to the matched project!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    tmux-session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$BASE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;$SELECTED&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# and alias this so I can just do `t bytes.zone` instead of having to type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# tmux_jump every time.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;alias&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;tmux_jump&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And... that&#x27;s it!
Hope you enjoy it!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Reducing Asset Size With Subsetting</title>
		<published>2020-03-03T00:00:00+00:00</published>
		<updated>2020-03-03T00:00:00+00:00</updated>
		<link href="https://bytes.zone/posts/reducing-asset-size-with-subsetting/" type="text/html"/>
		<id>https://bytes.zone/posts/reducing-asset-size-with-subsetting/</id>
		<content type="html">&lt;p&gt;&lt;strong&gt;Note:&lt;&#x2F;strong&gt; as of December 2020, this site no longer uses custom fonts.
This script is still useful, but you&#x27;re not loading the output in your browser &lt;em&gt;right now&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When I was building this site, I noticed that my web fonts were kind of big:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;52K	fonts&#x2F;OpenSans.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;48K	fonts&#x2F;Exo2-BoldItalic.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;24K	fonts&#x2F;Jetbrains-Mono.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;48K	fonts&#x2F;OpenSans-Italic.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;48K	fonts&#x2F;Exo2-Regular.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;48K	fonts&#x2F;Exo2-Bold.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;48K	fonts&#x2F;OpenSans-BoldItalic.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;52K	fonts&#x2F;OpenSans-Bold.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;368K	total&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you happened to load all the fonts with a cold cache, you&#x27;d be downloading a bit over a third of a megabyte.
These were by far the heaviest part of the site, especially compared to the HTML files.
Those are something like 12kb each, a quarter of the size of even one font.
I don&#x27;t like that difference, so let&#x27;s see if we can make them smaller!&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;But what&#x27;s in those files anyway?
Why are they so big?
Consider &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;NDISCOVER&#x2F;Exo-2.0&quot;&gt;Exo 2&lt;&#x2F;a&gt;, my heading font: in addition to the normal ASCII characters, it has a ton of Greek, Cyrillic, and Vietnamese characters.
This is great!
Tons of people speaking all kinds of languages can use this font to communicate… but since this site doesn&#x27;t use those characters, serving them doesn&#x27;t do anyone any good.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, this is a known problem, and we can solve it with something called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Subsetting&quot;&gt;subsetting&lt;&#x2F;a&gt;.
Subsetting, in general, is where you retrieve only the parts you need from a large data set.
In fonts, this means removing all the glyphs you don&#x27;t need from a font before you serve it.
Sounds like a plan!
But how?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;breaking-it-down&quot;&gt;Breaking it Down&lt;&#x2F;h2&gt;
&lt;p&gt;Well, since this is a static site, all the content is known in advance.
Hypothetically, that means that I can examine all the markdown files and figure out what characters I need to render it.
But when I thought about this more, I realized it wouldn&#x27;t work: what about the navigation and footer?
I also render headers and code samples in different fonts than I do the body copy.&lt;&#x2F;p&gt;
&lt;p&gt;Well, again, static site!
I can surely just inspect the output, right?
But another challenge there: the exact font—including things like bold and italic—is set with CSS.
Since normal, bold, and italic live in separate files, I need to calculate the style rules the way a browser would to be able to make the smallest possible subsets.&lt;&#x2F;p&gt;
&lt;p&gt;But that means… oh… I should probably just drive a headless browser, huh?&lt;&#x2F;p&gt;
&lt;p&gt;Sigh.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;accepting-the-inevitable&quot;&gt;Accepting the Inevitable&lt;&#x2F;h3&gt;
&lt;p&gt;I really didn&#x27;t want to set up a headless browser for this.
I had some bad experiences in the past trying to automate things with Selenium, not to mention all the pain I feel writing Capybara tests at work…
I felt like it would be slow, error-prone, and be hard to set up in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.netlify.com&quot;&gt;Netlify&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Well, good news if you&#x27;re in the same boat: the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;pptr.dev&quot;&gt;Puppeteer&lt;&#x2F;a&gt; project makes this way easier than it used to be!
I evaluated several options (including Servo and jsdom) before settling on it, and I was happily surprised!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;get-the-data-already&quot;&gt;Get the Data, Already!&lt;&#x2F;h2&gt;
&lt;p&gt;The basic strategy here looks like:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;start a headless Chrome instance with puppeteer&lt;&#x2F;li&gt;
&lt;li&gt;load an HTML page&lt;&#x2F;li&gt;
&lt;li&gt;find all the visible text nodes&lt;&#x2F;li&gt;
&lt;li&gt;remember their content and computed style&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That&#x27;s it; let&#x27;s go!
First we start the browser and load a page:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;async&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; puppeteer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;puppeteer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; browser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; = await&lt;&#x2F;span&gt;&lt;span&gt; puppeteer.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;launch&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; page&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; = await&lt;&#x2F;span&gt;&lt;span&gt; browser.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;newPage&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  for&lt;&#x2F;span&gt;&lt;span&gt; (file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; process.argv.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;slice&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; page.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;goto&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;file:&#x2F;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; file);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &#x2F;&#x2F; the rest of our script (next code block)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Aside: I don&#x27;t know if these should be &lt;code&gt;const&lt;&#x2F;code&gt; or &lt;code&gt;let&lt;&#x2F;code&gt;... I&#x27;ve heard both but the puppeteer docs use &lt;code&gt;const&lt;&#x2F;code&gt; so I&#x27;m going to, too&lt;&#x2F;p&gt;
&lt;p&gt;Then let&#x27;s walk the DOM to find all the text nodes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; fileOut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; = await&lt;&#x2F;span&gt;&lt;span&gt; page.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;evaluate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt; () {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; we have to define everything we need inline here, since `page.evaluate`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; runs everything in the browser context.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; uniqChars&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; char&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; text) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;      if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;out.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;includes&lt;&#x2F;span&gt;&lt;span&gt;(char)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        out.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(char);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    out.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;sort&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; out.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;join&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; we&amp;#39;re going to accumulate information about each text node in this object.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; see the next code snippet for how we actually use it.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; out&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; todo is a stack of nodes we have left to visit. We start with the top node&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; of the document and work our way down from there.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; todo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [window.document];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  while&lt;&#x2F;span&gt;&lt;span&gt; (todo.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; !==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; todo.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;shift&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &#x2F;&#x2F; `childNodes` is not an array, but you can still access its children by&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &#x2F;&#x2F; index. We are descending into the node so we just push all the children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &#x2F;&#x2F; onto the stack. It doesn&amp;#39;t matter much for this application whether we&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &#x2F;&#x2F; use a depth-first or a breadth-first search—we want to get all the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    &#x2F;&#x2F; nodees either way.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; node.childNodes.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt;; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      todo.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(node.childNodes[i]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (node.nodeName&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;#text&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;      &#x2F;&#x2F; the rest of our script (next code block)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; out;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally we can accumulate our calculated styles.
I do this in an object with the style information as the key (&lt;code&gt;out&lt;&#x2F;code&gt; above) so we can add characters to it easily:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; styles&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; window.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;getComputedStyle&lt;&#x2F;span&gt;&lt;span&gt;(node.parentElement);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; we don&amp;#39;t want to include text in invisible nodes (things like the guts of&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; &amp;lt;script&amp;gt; or &amp;lt;style&amp;gt; tags.) Just skip &amp;#39;em!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; (styles.display&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;none&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  continue&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; face&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; styles.fontFamily.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;split&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; fonts with spaces in the name get quoted, so we need to remove those.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  face&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; face.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;replace&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBEDFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; info&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    face: face,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    weight: styles.fontWeight,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    style: styles.fontStyle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; JSON&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;stringify&lt;&#x2F;span&gt;&lt;span&gt;(info);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (out[key]) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    out[key].chars&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; uniqChars&lt;&#x2F;span&gt;&lt;span&gt;(out[key].chars&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; node.textContent);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    out[key]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      font: info,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      chars:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; uniqChars&lt;&#x2F;span&gt;&lt;span&gt;(node.textContent)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, after analyzing each file, we combine the objects into a list.
(I haven&#x27;t shown this but you can see it &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;bytes.zone&#x2F;bytes.zone&#x2F;src&#x2F;commit&#x2F;7d957f13a7801ecbf1a5f663d263538214e44990&#x2F;script&#x2F;faces.js&quot;&gt;in the source as of this writing&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;p&gt;When the script returns, we end up with a JSON blob like this.
This is just &lt;a href=&quot;&#x2F;&quot;&gt;the homepage&lt;&#x2F;a&gt; and I&#x27;ve removed all the fallback fonts.
If I had passed more files into the script (for example, the output of &lt;code&gt;find&lt;&#x2F;code&gt;) I would see much more information in this array:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;font&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;face&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Exo 2&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;weight&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;700&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;style&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;normal&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;chars&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot; .HTbehnorstyz👋&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;font&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;face&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Exo 2&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;weight&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;400&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;style&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;normal&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;chars&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;acdeklopst&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;font&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;face&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Open Sans&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;weight&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;400&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      &amp;quot;style&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;normal&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    &amp;quot;chars&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot; !&amp;#39;,-.04:ABCDEHIJLMNORSTYabcdefghiklmnoprstuvwyz❤️&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are some interesting things happening here.
First, I mentioned that I only was using ASCII… well, that&#x27;s a bit of a lie; I&#x27;m also using a few emoji.
The script found and included these.
Second, I&#x27;m not even using &lt;em&gt;all&lt;&#x2F;em&gt; the ASCII characters; &lt;code&gt;q&lt;&#x2F;code&gt; and &lt;code&gt;x&lt;&#x2F;code&gt; are absent from Open Sans (my body font.)&lt;&#x2F;p&gt;
&lt;p&gt;True, this is just for one page, but it holds across the rest of the content: each font ends up only needing to include well under 100 glyphs to be able to render everything on the whole site.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;subsetting-for-real&quot;&gt;Subsetting, for Real&lt;&#x2F;h2&gt;
&lt;p&gt;After all that analysis, how do we actually subset the fonts?&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m using a tool called &lt;code&gt;pyftsubset&lt;&#x2F;code&gt;, available in the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;fonttools&#x2F;fonttools&quot;&gt;fonttools&lt;&#x2F;a&gt; Python package.
Put simply, to subset a font, you need to call &lt;code&gt;pyftsubset {input font} --unicodes={code points}&lt;&#x2F;code&gt;.
There are a few more flags in the actual script I use, but that&#x27;s basically it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;bytes.zone&#x2F;bytes.zone&#x2F;src&#x2F;commit&#x2F;7d957f13a7801ecbf1a5f663d263538214e44990&#x2F;script&#x2F;subset.py&quot;&gt;The script itself&lt;&#x2F;a&gt; is not terribly exciting, so I&#x27;m not going to show much of it here.
It basically slurps down the output of the face-finding script, calls &lt;code&gt;pyftsubset&lt;&#x2F;code&gt; on the result, and prints some statistics about the filesize difference:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Finding subsets of 8 fonts on 10 pages&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Subset .&#x2F;dist&#x2F;fonts&#x2F;Exo2-Bold.woff2 from 46656 to 5608 bytes (12.02% of original size, 61 glyphs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Subset .&#x2F;dist&#x2F;fonts&#x2F;OpenSans.woff2 from 50116 to 8240 bytes (16.44% of original size, 81 glyphs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Subset .&#x2F;dist&#x2F;fonts&#x2F;Exo2-Regular.woff2 from 46300 to 3440 bytes (7.43% of original size, 35 glyphs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Subset .&#x2F;dist&#x2F;fonts&#x2F;Jetbrains-Mono.woff2 from 22368 to 10564 bytes (47.23% of original size, 87 glyphs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Subset .&#x2F;dist&#x2F;fonts&#x2F;OpenSans-Italic.woff2 from 48148 to 3668 bytes (7.62% of original size, 18 glyphs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Subset .&#x2F;dist&#x2F;fonts&#x2F;OpenSans-Bold.woff2 from 51932 to 4548 bytes (8.76% of original size, 27 glyphs)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And looking at the final font sizes, I feel much better aabout shipping these to people&#x27;s browsers.
With this result, I can load all the glyphs in all the fonts I need in less size than just one of the original font files:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;12K	dist&#x2F;fonts&#x2F;OpenSans.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;12K	dist&#x2F;fonts&#x2F;Jetbrains-Mono.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;4.0K	dist&#x2F;fonts&#x2F;OpenSans-Italic.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;4.0K	dist&#x2F;fonts&#x2F;Exo2-Regular.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8.0K	dist&#x2F;fonts&#x2F;Exo2-Bold.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8.0K	dist&#x2F;fonts&#x2F;OpenSans-Bold.woff2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;48K	total&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Aside: I also have some uncompressed-but-unused files in the output of my site, but they aren&#x27;t ever referenced, so a browser shouldn&#x27;t ever load them.
I&#x27;m not counting those towards the filesize total here.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;success&quot;&gt;Success!&lt;&#x2F;h2&gt;
&lt;p&gt;In the end, I reduced the size of my font files by about 87%, going from 386K to 48K.
The final subsets have about 300 total characters to render my site.
While this will grow over time as I add more content, it should level out pretty quickly.&lt;&#x2F;p&gt;
&lt;p&gt;In short: it worked!&lt;&#x2F;p&gt;
&lt;p&gt;So, in writing about this, would I recommend you do it too?
Solid &lt;strong&gt;maybe&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;I think it makes sense to do this on a site where:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You&#x27;re using custom fonts.&lt;&#x2F;strong&gt; If you&#x27;re using generic font names (like &lt;code&gt;sans-serif&lt;&#x2F;code&gt; or &lt;code&gt;monospace&lt;&#x2F;code&gt;) or your font stack is a mix of built-ins and system fonts (e.g. &lt;code&gt;Helvetica, Arial, sans-serif&lt;&#x2F;code&gt;), there&#x27;s no need to save on bandwidth here; your readers already have the whole font!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;All your content is known in advance.&lt;&#x2F;strong&gt; If you have anything that&#x27;s dynamically loaded, you&#x27;ll get really weird results where some—but not all—of your characters will be replaced by the fallback font.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Fonts are actually the largest asset class.&lt;&#x2F;strong&gt; If you&#x27;re serving a 10mb JPEG as a background image, compressing that is a lot easier and will be a much better experience for your readers than figuring out a subsetting pipeline for your site. The framework I use, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elm-pages.com&quot;&gt;elm-pages&lt;&#x2F;a&gt;, takes care of compressing images for me. If yours doesn&#x27;t, searching around for image optimization tools will probably net you some easy wins.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;You don&#x27;t care about cache misses (in the short term.)&lt;&#x2F;strong&gt; Your custom subset is all but guaranteed not to be in a visitor&#x27;s cache. For a site like mine, a really high percent of visitors are likely to be new. That makes me feel more OK about serving fresh font files every time. But a big note here: on a site with more content, the subsets will tend to stabilize to the characters you actually use. In the long run, the fonts won&#x27;t be quite this small but they will be much stabler.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If your site meets all those conditions, give it a try!
I&#x27;ve linked to the source of my scripts in the page above, and you can check out the full latest source for this site at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.bytes.zone&#x2F;bytes.zone&#x2F;bytes.zone&quot;&gt;git.bytes.zone&#x2F;bytes.zone&#x2F;bytes.zone&lt;&#x2F;a&gt;. (is it just me or is it echoing in here?)&lt;&#x2F;p&gt;
&lt;p&gt;Have fun!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>a batch at the Recurse Center</title>
		<published>2020-01-06T00:00:00+00:00</published>
		<updated>2020-01-10T00:00:00+00:00</updated>
		<link href="https://bytes.zone/projects/attend-recurse-center-m1-2020/" type="text/html"/>
		<id>https://bytes.zone/projects/attend-recurse-center-m1-2020/</id>
		<content type="html">&lt;p&gt;In January of 2020, I attended one of the last in-person batches of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.recurse.com&#x2F;&quot;&gt;the Recurse Center&lt;&#x2F;a&gt; in Brooklyn, New York.
I worked on an implementation of the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mxgmn&#x2F;WaveFunctionCollapse&quot;&gt;Wave Function Collapse algorithm&lt;&#x2F;a&gt; (the game thing, not the quantum thing) in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elm-lang.org&#x2F;&quot;&gt;Elm&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d recommend attending the Recurse Center to anyone!
The facilitators have built a really great community around &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.recurse.com&#x2F;social-rules&quot;&gt;some well-thought-out shared values&lt;&#x2F;a&gt;.
I only had time for a mini batch (1 week), but I&#x27;d like to go back for a full 6– or 12–week session!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Robot Buttons from Mars</title>
		<published>2019-04-26T00:00:00+00:00</published>
		<updated>2019-04-26T00:00:00+00:00</updated>
		<link href="https://bytes.zone/talks/robot-buttons-from-mars/" type="text/html"/>
		<id>https://bytes.zone/talks/robot-buttons-from-mars/</id>
		<content type="html">&lt;figure class=&quot;youtube-embed&quot;&gt;
  &lt;div&gt;
    &lt;iframe src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;PDyWP-0H4Zo&quot; title=&quot;Robot Buttons from Mars&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen frameborder=0&gt;&lt;&#x2F;iframe&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Joyful Particles in Elm</title>
		<published>2019-02-21T00:00:00+00:00</published>
		<updated>2019-02-21T00:00:00+00:00</updated>
		<link href="https://bytes.zone/talks/joyful-particles-in-elm/" type="text/html"/>
		<id>https://bytes.zone/talks/joyful-particles-in-elm/</id>
		<content type="html">&lt;figure class=&quot;youtube-embed&quot;&gt;
  &lt;div&gt;
    &lt;iframe src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;goL7LeDHFi4&quot; title=&quot;Joyful Particles in Elm&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen frameborder=0&gt;&lt;&#x2F;iframe&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>The State of Elm 2017 (Extended Edition)</title>
		<published>2017-06-16T00:00:00+00:00</published>
		<updated>2017-06-16T00:00:00+00:00</updated>
		<link href="https://bytes.zone/talks/the-state-of-elm-2017/" type="text/html"/>
		<id>https://bytes.zone/talks/the-state-of-elm-2017/</id>
		<content type="html">&lt;figure class=&quot;youtube-embed&quot;&gt;
  &lt;div&gt;
    &lt;iframe src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;NKl0dtSe8rs&quot; title=&quot;The State of Elm 2017 (Extended Edition)&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen frameborder=0&gt;&lt;&#x2F;iframe&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
</content>
	</entry>
</feed>
