<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://mniip.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mniip.com/" rel="alternate" type="text/html" /><updated>2026-07-11T14:27:42+00:00</updated><id>https://mniip.com/feed.xml</id><title type="html">mniip.com</title><author><name>mniip</name></author><entry><title type="html">ICFP Contest 2024 Write-up By Team powder</title><link href="https://mniip.com/posts/2024-07-03-icfpc-2024" rel="alternate" type="text/html" title="ICFP Contest 2024 Write-up By Team powder" /><published>2024-07-03T00:00:00+00:00</published><updated>2024-07-03T00:00:00+00:00</updated><id>https://mniip.com/posts/icfpc-2024</id><content type="html" xml:base="https://mniip.com/posts/2024-07-03-icfpc-2024"><![CDATA[<p>The International Conference on Functional Programming holds an annual programming contest to attract attention to applications of functional programming. This isn’t your average competitive programming contest. The problems are usually designed in some way around functional programming and programming language theory concepts, rather than raw number crunching. More often than not, they are also NP-complete (or worse), and it isn’t expected that anyone comes up with a perfect solution. A typical approach involves some amalgamation of slow exact algorithms, fast approximate algorithms, and even solutions by hand. It is a 72-hour marathon of coming up with the best heuristics to an unsolvable problem.</p>

<p>I participated in the contest in a 2-person team (powder: <a href="https://mniip.com/">@mniip</a>, <a href="https://github.com/savask/">@savask</a>), using primarily Haskell. Here’s how it went.
<!-- more --></p>

<p><a href="https://icfpcontest2024.github.io/task.html">This year</a> the contestants were given a “communication channel” to a mysterious system (School of the Bound Variable), which accepts and returns encoded expressions in a simple lambda-calculus-based language with the following grammar (called ICFP expressions):</p>

<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">Expr</span> <span class="o">=</span> <span class="kt">EBool</span> <span class="kt">Bool</span> <span class="o">|</span> <span class="kt">EInt</span> <span class="kt">Natural</span> <span class="o">|</span> <span class="kt">EText</span> <span class="kt">Text</span> <span class="c1">-- data</span>
  <span class="o">|</span> <span class="kt">EUnary</span> <span class="kt">UnaryOp</span> <span class="kt">Expr</span> <span class="o">|</span> <span class="kt">EBinary</span> <span class="kt">BinaryOp</span> <span class="kt">Expr</span> <span class="kt">Expr</span> <span class="c1">-- operations</span>
  <span class="o">|</span> <span class="kt">EIf</span> <span class="kt">Expr</span> <span class="kt">Expr</span> <span class="kt">Expr</span>
  <span class="o">|</span> <span class="kt">ELambda</span> <span class="kt">Natural</span> <span class="kt">Expr</span> <span class="o">|</span> <span class="kt">EVar</span> <span class="kt">Natural</span> <span class="c1">-- lambda calculus (see Apply below)</span>

<span class="kr">data</span> <span class="kt">UnaryOp</span> <span class="o">=</span> <span class="kt">Negate</span> <span class="o">|</span> <span class="kt">Not</span> <span class="o">|</span> <span class="kt">EncodeBase94</span> <span class="o">|</span> <span class="kt">DecodeBase94</span>

<span class="kr">data</span> <span class="kt">BinaryOp</span> <span class="o">=</span> <span class="kt">Add</span> <span class="o">|</span> <span class="kt">Sub</span> <span class="o">|</span> <span class="kt">Mul</span> <span class="o">|</span> <span class="kt">Quot</span> <span class="o">|</span> <span class="kt">Rem</span> <span class="c1">-- EInt operations</span>
  <span class="o">|</span> <span class="kt">Less</span> <span class="o">|</span> <span class="kt">Greater</span> <span class="o">|</span> <span class="kt">Equal</span> <span class="c1">-- comparison</span>
  <span class="o">|</span> <span class="kt">Or</span> <span class="o">|</span> <span class="kt">And</span> <span class="c1">-- EBool operations</span>
  <span class="o">|</span> <span class="kt">Concat</span> <span class="o">|</span> <span class="kt">Take</span> <span class="o">|</span> <span class="kt">Drop</span> <span class="c1">-- EString operations</span>
  <span class="o">|</span> <span class="kt">Apply</span> <span class="c1">-- Apply lambda function to argument</span>
</code></pre></div></div>

<p>The mysterious system uses a wacky character encoding with only 94 characters for its strings, and <code class="language-plaintext highlighter-rouge">EncodeBase94</code>/<code class="language-plaintext highlighter-rouge">DecodeBase94</code> convert between strings and natural numbers using base-94.</p>

<h2 id="hello">Hello</h2>

<p>Learning to communicate with this server earns the team points for the <code class="language-plaintext highlighter-rouge">hello</code> task. E.g. sending the string <code class="language-plaintext highlighter-rouge">"get index"</code> (encoded as <code class="language-plaintext highlighter-rouge">S'%4}).$%8</code>) would return a text document containing an index page with pointers to other places of interest. Initially the server would simply return a string literal (an <code class="language-plaintext highlighter-rouge">EText</code> expression), but sometimes it would return unevaluated programs and we have to evaluate them. This culminates in the “language test”, a program sent by the server that validates each operation in the ICFP expression language, and if successful, gives you a magic incantation that you can send to the server to score points for it.</p>

<p>We implemented a very naive big-step term rewriting evaluator. The task prescribes call-by-name (not call-by-need, no thunks!) and we did just that, and that was mostly enough until the end of the contest.</p>

<h2 id="lambdaman">LambdaMan</h2>

<p>Digging deeper into the server we discover a task description for what amounts to the following problem: given a maze on a rectangular grid, you have to give a sequence of directions that visits every free cell at least once. Your score is the size of your input (lower is better).</p>

<p>Not the length of the sequence of directions, but rather the length of the ICFP expression you send to the server. That means for repetitive strings it’s better to send a program that evaluates to the needed string, instead of just the string literal itself.</p>

<p>At this point we figured we’ll be needing to write a lot of programs in this language, so we implemented a translator from Haskell syntax to this language. Most of the work is done by <code class="language-plaintext highlighter-rouge">template-haskell</code> (Haskell’s proc macro system), and all we have to do is map the Haskell AST constructs to ICFP expressions. Notably, every single lambda and variable counts, so we don’t do any desugaring that hides code size.</p>

<p>Finding the shortest program that outputs a given string is an instance of <a href="https://en.wikipedia.org/wiki/Kolmogorov_complexity">Kolmogorov complexity</a>, which is actually equivalent to the halting problem, so no perfect solution is possible.</p>

<p>For any maze we can generate a string that solves it and then compress it, although generating the <em>shortest</em> string is not that easy, and I conjectured that it’s NP-complete. For compression, we’ve tried a combination of run-length-encoding, common substring elimination, and conversion to base-4. A couple mazes can be solved by always repeating every step twice, which further halves the size of the base-4 encoding.</p>

<p><code class="language-plaintext highlighter-rouge">template-haskell</code> allows some slick staged metaprogramming here, and our code looked something like this:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lambdaman5</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="n">lift</span> <span class="o">.</span> <span class="n">fromHaskell</span> <span class="o">=&lt;&lt;</span> <span class="p">[</span><span class="o">|</span>
    <span class="s">"solve lambdaman5 "</span> <span class="o">&lt;&gt;</span>
    <span class="kr">let</span> <span class="n">go</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="n">baseDecoder</span> <span class="s">"UDLR"</span><span class="p">)</span>
    <span class="kr">in</span> <span class="n">go</span> <span class="n">go</span> <span class="o">$</span><span class="p">(</span><span class="n">lift</span> <span class="o">$</span> <span class="n">encodeBase</span> <span class="s">"UDLR"</span> <span class="s">"RDLLLULU[...]UULULLLL"</span><span class="p">)</span>
  <span class="o">|</span><span class="p">])</span>
</code></pre></div></div>
<p>This alternates execution of actual Haskell code (<code class="language-plaintext highlighter-rouge">encodeBase</code>), and Haskell syntax that is merely translated into ICFP expressions (<code class="language-plaintext highlighter-rouge">let go =</code>), and ultimately produces this submission:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"solve lambdaman5 "</span> <span class="o">&lt;&gt;</span> <span class="kr">let</span>
    <span class="n">x0</span> <span class="o">=</span> <span class="nf">\</span><span class="n">x1</span> <span class="n">x0</span> <span class="o">-&gt;</span> <span class="kr">if</span> <span class="n">x1</span> <span class="o">==</span> <span class="mi">0</span>
      <span class="kr">then</span> <span class="s">""</span>
      <span class="kr">else</span> <span class="n">take</span> <span class="mi">1</span> <span class="p">(</span><span class="n">drop</span> <span class="p">(</span><span class="n">x1</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">4</span><span class="p">)</span> <span class="s">"UDLR"</span><span class="p">)</span> <span class="o">&lt;&gt;</span> <span class="n">x0</span> <span class="n">x0</span> <span class="p">(</span><span class="n">x1</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mi">4</span><span class="p">)</span>
  <span class="kr">in</span> <span class="n">x0</span> <span class="n">x0</span> <span class="mi">11246230</span><span class="p">[</span><span class="o">...</span><span class="p">]</span><span class="mi">19159975</span>
</code></pre></div></div>

<p>On the other hand, for more regular-looking mazes we can manually come up with a short and hand-optimized program that generates a solution according to a particular pattern. For example, this is a very compact program that sweeps out a 27x27 rectangle:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">let</span>
  <span class="n">thrice</span> <span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">&lt;&gt;</span> <span class="n">x</span> <span class="o">&lt;&gt;</span> <span class="n">x</span>
  <span class="n">times27</span> <span class="n">x</span> <span class="o">=</span> <span class="n">thrice</span> <span class="p">(</span><span class="n">thrice</span> <span class="p">(</span><span class="n">thrice</span> <span class="n">x</span><span class="p">))</span>
<span class="kr">in</span> <span class="n">times27</span> <span class="p">(</span><span class="n">times27</span> <span class="s">"R"</span> <span class="o">&lt;&gt;</span> <span class="s">"D"</span> <span class="o">&lt;&gt;</span> <span class="n">times27</span> <span class="s">"L"</span><span class="p">)</span>
</code></pre></div></div>
<p>Of note is that direction commands that would run you into a wall are simply ignored, and your program can output a lot of these “waste” commands if it helps keep the program short. <code class="language-plaintext highlighter-rouge">times27</code> made a frequent appearance because its code is just so short, even if we only needed to repeat something like 4 times.</p>

<p>At some point we discussed the idea of outputting random walks, but quickly dismissed it because we thought it would be unlikely that a random walk could actually fill the entire maze given a reasonable amount of steps. Turns out many other teams successfully employed this very strategy!</p>

<h2 id="spaceship">Spaceship</h2>

<p>Another available task was to control an inertial spaceship to make it visit a given set of points on a plane.</p>

<p>The input is a sequence of accelerations at each time step. The spaceship is capable of accelerating by -1, 0, or +1 in both the X and Y directions. At each time step, this acceleration is first added to its velocity, and then the resulting velocity is added to the position. A point counts as visited if the spaceship was at that point at the end of the time step. The score is the number of time steps taken (no program compression shenanigans this time).</p>

<p>A lot about the spaceship’s motion can actually be expressed analytically, and this opens the door to a rather efficient approach to the problem.</p>

<p>Reducing the problem to 1 dimension, if the spaceship is moving at constant acceleration $a$, starting from velocity $v_0$ and position $x_0$, then at time $t$ its position becomes:</p>

\[x = a\frac{t(t+1)}{2} + v_0t + x_0\]

<p>The deviation from the standard parabolic motion equation is due to the discretization of time.</p>

<p>If the acceleration is not constant, then the velocity always changes by the sum of the accelerations, independent of the order of the commands. However the change in the position does depend on the relative order of the commands.</p>

<p>A key observation here is if that we put a bunch of <code class="language-plaintext highlighter-rouge">-1</code>’s first, and then a bunch of <code class="language-plaintext highlighter-rouge">+1</code>’s, then that would correspond to the <em>most negative</em> change in position, <em>for a given change in velocity, and given duration</em>. Likewise, putting all <code class="language-plaintext highlighter-rouge">+1</code>’s first, and all <code class="language-plaintext highlighter-rouge">-1</code>’s after, corresponds to the <em>most positive</em> change in position for a given change in velocity and given duration. These represent two extremal “strategies” of changing your velocity.</p>

<p>A second key observation is that every intermediate change in position is attainable by some intermediate strategy. The set $S(t, v_0, v_1)$ of positions attainable in $t$ steps while also changing your velocity from $v_0$ to $v_1$ is the union of the three sets</p>

\[S(t, v_0, v_1) = \bigcup_{a \in \{-1, 0, +1\}} S(t - 1, v_0, v_1 - a) + v_1\]

<p>where $a$ is the acceleration you use at the last step. The extremal strategies line up so that these three sets always overlap, and by induction on $t$ we conclude that $S(t, v_0, v_1)$ is always a consecutive range.</p>

<p>Thus the inequalities for the two extremal strategies act as an analytical expression of when a specific maneuver is possible:</p>

\[t^2 + 2t(v_1 + v_0) - (v_1 - v_0)^2 + 2(v_1 - v_0) - 4(x_1 - x_0) \ge 0\]

\[t^2 - 2t(v_1 + v_0) - (v_1 - v_0)^2 - 2(v_1 - v_0) + 4(x_1 - x_0) \ge 0\]

<p>We constructed these as conditions on $x_1$, but we can just as well use them as conditions on $t$ given $v_0, x_0, x_1, v_1$. From the fact that these are quadratic inequalities and that $t$ must be non-negative, we can conclude that the set of solutions for $t$ is the union of a ray and at most one range, and we can cheaply and exactly compute the endpoints of these rays/ranges from roots of the quadratics.</p>

<p>Going back to 2 dimensions, we have restrictions on $t$ from the X coordinate, and also restrictions on $t$ from the Y coordinate. These can be intersected analytically to give us a cheap algorithm to compute the smallest possible $t$ necessary to go from phase state $(x_0, y_0, v_{x0}, v_{y0})$ to phase state $(x_1, y_1, v_{x1}, v_{y1})$.</p>

<p>We can also analytically minimize the quadratics along the $v_1$ variable, and repeat the same process to get an algorithm that computes the smallest duration necessary to go to a specific point regardless of the exit velocity.</p>

<p>Once we know that a specific maneuver is possible within some time, it’s easy to reconstruct the accelerations necessary to actually perform it. That means there’s two things left to this problem: figuring out an order for visiting the points, and figuring out what velocity we’ll have at each point, both so as to minimize the sum of maneuver durations (which is cheap to calculate).</p>

<p>At first we focused solely on the order, leaving the velocities to a greedy algorithm. Here’s a couple typical inputs for the spaceship problem:</p>
<figure>
    <img src="/static/img/icfpc2024/spaceship-input.png" alt="On the left, a collection of points arranged along a self-intersecting curve. On the right, a collection of points scattered completely randomly and uniformly in a square." style="max-width: 100%" />
</figure>

<p>For the left case, the “intended” route is rather obvious, perhaps modulo choosing which branch to follow near a self-intersection. For the right case, not so much. This problem seems actually fairly similar to the <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">Traveling Salesman Problem</a>, but with one small twist.</p>

<p>First we wrote an implementation of simulated annealing to solve TSP. We would generate random ranges of indexes to reverse, which is equivalent to picking a random pair of edges and reconfiguring them from an “<code class="language-plaintext highlighter-rouge">X</code>” to a “<code class="language-plaintext highlighter-rouge">) (</code>”, or vice versa – and then see how much the score improved or worsened. Simulated annealing would generate routes like this (shown with the maneuvers taken by the spaceship):</p>
<figure>
    <img src="/static/img/icfpc2024/spaceship-annealed-distance.png" alt="An untangled curve visiting points uniformly spread across a square" style="max-width: 100%" />
</figure>

<p>The twist is that using distance as a metric generates a lot of sharp turns that the spaceship has great trouble traversing because it has inertial movement. What we really want to optimize is not the distance (change in position), but change in velocity. Much like optimizing distance makes each 2-point route a straight line segment, we want to make each 3-point route a parabola. The end metric we arrived at is to minimize, for each three consecutive points $(x_0, y_0)$, $(x_1, y_1)$, $(x_2, y_2)$, the value:</p>

\[\max(\left\vert x_0 - 2x_1 + x_2 \right\vert, \left\vert y_0 - 2y_1 + y_2 \right\vert)\]

<p>This counts the minimal acceleration required to traverse these 3 points in sequence. We can then generate routes like this:</p>
<figure>
    <img src="/static/img/icfpc2024/spaceship-annealed-momentum.png" alt="A tangled curve that is much more smooth, visiting points uniformly spread across a square" style="max-width: 100%" />
</figure>
<p>It looks much more convoluted, but actually wins 7% in terms of the final score.</p>

<p>After fixing a route, we can similarly use simulated annealing to pick velocities to use at each point, this time minimizing the actual resulting score – sum of times necessary for the maneuvers. Here though we have to be careful about what we consider to be neighboring states. In some cases, the space of maneuvers might be discontinuous, and not at all due to the discretization. When $x_1 - x_0 \lessapprox v_0^2/4$, it means that you’re moving too fast to gently stop at the point $x_1$, and you have to either go through it, or turn around and go back to it.</p>

<p>Later we also tried writing an annealer that tries to shuffle both simultaneously – the route and the velocities. That didn’t bring much profit.</p>

<p>Unfortunately we didn’t come up with any clever algorithm to reliably detect the order of points in the inputs that were stretched out in a curve, so we resorted to also using simulated annealing for those, which struggled at larger inputs.</p>

<p>Struggling at large inputs is also why we couldn’t come up with a route for the last (25th) input. In the end we simply manually ordered the points in a thick square spiral around the center, and let maneuver annealing do the rest (right side is the center zoomed in):</p>
<figure>
    <img src="/static/img/icfpc2024/spaceship-25.png" alt="Chicken scratch in a spiral" style="max-width: 100%" />
</figure>

<p>On a cool but useless aside, the first solutions we had for the 24th and 25th inputs were so large they did not fit in the 1MB submission limit, and I’ve wasted a bit of time on trying to compress them (by making a program that fits within 1MB and evaluates to the submission). Simple base-9 (not 4 this time) encoding didn’t work because the decoder was actually hitting a time limit due to the quadratic complexity of the arithmetic operations it had to perform to unpack. Instead I tried to make good use of declaring common substrings as variables and then using concatenation. In the end I managed to fit a 2.9MB solution in a 0.8MB program. This proved to be useless because in the end we had solutions for these inputs that fit under 1MB.</p>

<h2 id="efficiency">Efficiency</h2>

<p>We tackled the 5th available task (<code class="language-plaintext highlighter-rouge">efficiency</code>) before we did the 4th (<code class="language-plaintext highlighter-rouge">3d</code>). To even access the task we had to “exploit” a “backdoor”: one of the inputs for <code class="language-plaintext highlighter-rouge">spaceship</code> was actually encoded as a program that outputs the string, and in the unevaluated program there was an unused string literal containing <code class="language-plaintext highlighter-rouge">"/etc/passwd"</code>. Following the lead, we were able to find out the Headmaster’s password by cracking its MD5 hash (luckily the password was so common the hash could simply be googled). This was one of the three ways to unlock this task, the other two being solving a few <code class="language-plaintext highlighter-rouge">3d</code> problems, or waiting until the end of the lightning round of the contest (first 24 hours).</p>

<p>The task itself consists of 25 programs the server sends you, which you have to evaluate. We quickly found that our evaluator is not fast enough to handle these programs. I thought of reusing a cool trick that helped us win the 2020 lightning division: much like we can translate Haskell syntax into ICFP expressions, we can translate ICFP expressions into Haskell, and compile them using GHC. Very curiously, these programs actually interact uniquely poorly with the GHC simplifier, causing it to run out of memory and stack space!</p>

<p>While I was battling these problems, my teammate started reverse engineering the programs and discovered the true nature of the issue. These programs cannot be run on modern computers! (maybe except a couple)</p>

<p>For example, here’s the 4th program:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">\</span><span class="n">x1</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="nf">\</span><span class="n">x2</span> <span class="o">-&gt;</span> <span class="n">x1</span> <span class="p">(</span><span class="n">x2</span> <span class="n">x2</span><span class="p">))</span> <span class="p">(</span><span class="nf">\</span><span class="n">x2</span> <span class="o">-&gt;</span> <span class="n">x1</span> <span class="p">(</span><span class="n">x2</span> <span class="n">x2</span><span class="p">)))</span>
<span class="p">(</span><span class="nf">\</span><span class="n">x4</span> <span class="n">x3</span> <span class="o">-&gt;</span> <span class="kr">if</span> <span class="n">x4</span> <span class="o">&lt;</span> <span class="mi">2</span> <span class="kr">then</span> <span class="mi">1</span> <span class="kr">else</span> <span class="n">x3</span> <span class="p">(</span><span class="n">x4</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">x3</span> <span class="p">(</span><span class="n">x4</span> <span class="o">-</span> <span class="mi">2</span><span class="p">))</span> <span class="mi">40</span>
</code></pre></div></div>
<p>Recognizing the <code class="language-plaintext highlighter-rouge">Y</code> combinator used for recursion, we can rewrite the program as follows:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">let</span>
  <span class="n">x3</span> <span class="n">x4</span> <span class="o">=</span> <span class="kr">if</span> <span class="n">x4</span> <span class="o">&lt;</span> <span class="mi">2</span>
    <span class="kr">then</span> <span class="mi">1</span>
    <span class="kr">else</span> <span class="n">x3</span> <span class="p">(</span><span class="n">x4</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">x3</span> <span class="p">(</span><span class="n">x4</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
<span class="kr">in</span> <span class="n">x3</span> <span class="mi">40</span>
</code></pre></div></div>
<p>Clearly, <code class="language-plaintext highlighter-rouge">x3</code> is simply a naive recursive implementation of Fibonacci numbers, and the program is trying to evaluate the 40th Fibonacci number.</p>

<p>A compiler like GHC will never guess that it should actually completely rewrite the program using memoization/dynamic programming, that job lies on us.</p>

<p>Some of these problems were number theory exercises, like computing Mersenne primes. Others were straight up logic puzzles. For example, the 7th program looks like this, after some desugaring:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">let</span>
  <span class="n">f</span> <span class="n">n</span> <span class="o">=</span> <span class="kr">let</span>
        <span class="n">x1</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x1</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
        <span class="n">x2</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x2</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
        <span class="n">x3</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x4</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
        <span class="n">x4</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x8</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
        <span class="n">x5</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x10</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
        <span class="cm">{- ... -}</span>
        <span class="n">x39</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x4000000000</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
        <span class="n">x40</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">`</span><span class="n">quot</span><span class="p">`</span> <span class="mh">0x8000000000</span> <span class="p">`</span><span class="n">rem</span><span class="p">`</span> <span class="mi">2</span>
    <span class="kr">in</span> <span class="kr">if</span>
        <span class="p">(</span><span class="n">not</span> <span class="n">x18</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x15</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">not</span> <span class="n">x14</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x9</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
        <span class="p">(</span><span class="n">not</span> <span class="n">x19</span> <span class="o">||</span> <span class="n">x37</span> <span class="o">||</span> <span class="n">x12</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">not</span> <span class="n">x39</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">not</span> <span class="n">x20</span> <span class="o">||</span> <span class="n">x18</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
        <span class="p">(</span><span class="n">not</span> <span class="n">x8</span> <span class="o">||</span> <span class="n">x16</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x24</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">not</span> <span class="n">x29</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x39</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
        <span class="cm">{- ... -}</span>
        <span class="p">(</span><span class="n">not</span> <span class="n">x16</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x37</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">x22</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x14</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
        <span class="p">(</span><span class="n">x28</span> <span class="o">||</span> <span class="n">x8</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x29</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">x1</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">not</span> <span class="n">x24</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x32</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
        <span class="p">(</span><span class="n">not</span> <span class="n">x2</span> <span class="o">||</span> <span class="n">not</span> <span class="n">x19</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">not</span> <span class="n">x31</span> <span class="o">||</span> <span class="n">x13</span> <span class="o">||</span> <span class="n">x14</span><span class="p">)</span>
      <span class="kr">then</span> <span class="n">n</span>
      <span class="kr">else</span> <span class="n">f</span> <span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="kr">in</span> <span class="n">f</span> <span class="mi">1</span>
</code></pre></div></div>

<p>This program splits a number into 40 bits, and tests whether they satisfy a boolean CNF formula. If not, it tries with the next number, and so on, until a match is found. Of course, finding a bit pattern that satisfies a boolean formula is a well studied problem. It’s NP-complete, but SAT-solvers are off the shelf implementations of some state of the art algorithms in this area, and indeed we were able to wrangle Z3 into solving these, including outputting the lexicographically smallest solution (which is what the program is supposed to output). Alongside the CNF formulas there were also a couple Sudokus (literally), which we also solved using Z3.</p>

<h2 id="3d">3D</h2>

<p>The task we skipped so far was called <code class="language-plaintext highlighter-rouge">3d</code>. It defined an esoteric programming language, programmed on a 2-dimensional grid, which then evolves in a third dimension of time, with multiverse time travel! It’s one of those languages where you look at the spec and think: “How do you even do <em>anything</em>?”.</p>

<p>Everything on the grid is either a number or a device. You have devices to move stuff around the grid, you have devices to do simple arithmetic operations on the numbers (addition, subtraction, multiplication, <code class="language-plaintext highlighter-rouge">quot</code>, <code class="language-plaintext highlighter-rouge">rem</code>), you have devices that test for equality and inequality.</p>

<p>And you have a device that lets you send a cell to some specific location some specific time ago. Doing this rollbacks the execution of the program to that moment and restarts it from there.</p>

<p>The task was to solve a number of programming exercises in this language. Test inputs would spawn on the locations you designated as inputs, and the program would execute until a number is pushed into a cell you designated as the output. Assuming the answer is correct, your program would be scored based on the 3-dimensional bounding box of spacetime in which it fit during its execution (lower volume is better).</p>

<p>The first exercise was implementing a factorial function. You can use numbers traveling in literal cycles as loops, but that unrolls the entire loop in the time dimension, giving you a terrible score. Instead you want to implement loops using time travel, which if set up correctly, makes it so that your program’s time dimension is bounded from above, no matter the number of iterations of the loop.</p>

<p>Using time travel poses a significant challenge though: if you have some mechanism that results in time travel being triggered, the mechanism is rolled back and will trigger time travel again, unless somehow disabled. Thus it’s easy to get stuck in an unintentional time loop, and hard to make programs using time travel composable, which is necessary for writing larger and larger programs.</p>

<p>At some point we did implement a simulator, but we didn’t implement a grid-based editor or anything like that, resorting to constructing and laying out programs manually in a text editor.</p>

<p>A few ideas that we did come up with and used in our solutions:</p>

<ul>
  <li>Computing <code class="language-plaintext highlighter-rouge">(3*x + 2) `div` (3*x + 1)</code> tests whether <code class="language-plaintext highlighter-rouge">x</code> is positive, negative, or zero.</li>
  <li>Computing <code class="language-plaintext highlighter-rouge">(2*x + 1) `rem` 2</code> yields <code class="language-plaintext highlighter-rouge">-1</code> for negative x, and <code class="language-plaintext highlighter-rouge">1</code> for non-negative x, as a simplified test of positivity.</li>
  <li>More than one cell can be designated as input (the input is cloned), and as output (first one ends the program).</li>
  <li>Sometimes a long arithmetic circuit can be divided into two, where the output of the first half sends values into the past to the second half. This slightly increases space footprint but decreases time footprint.</li>
  <li>The solution for problem 10 (balanced parentheses and brackets) can be reused to solve problem 9 (balanced parentheses).</li>
</ul>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>The contest was great and we had lots of fun. The problems were very diverse, but not too diverse so as to be intractable by a small team.</p>

<p>Solving the engineering challenges of probabilistic algorithms has always been a weak point for our team, but this year I finally felt like I understood what I was doing with simulated annealing and that I was getting useful results with it.</p>

<p>Tooling has also been a weak point: during previous contests we always went with the path of least resistance, which is to make a bunch of CLI programs that consume text files with problems and produce more text files with solutions, and then glue everything together with bash. This approach doesn’t really scale as you start having multiple solvers for a problem, or algorithms that improve on existing solutions, and then you want to track which solution came from where, whether they have been “improved” already, and by what, etc. Likewise, probabilistic algorithms tend to need a lot of tinkering with parameters, and may need checkpoints and so on. And of course, you have to share solutions between team members.</p>

<p>Unfortunately this year was no different. Last year I set out to change that, so before the contest I set up a database that could be used to manage problems and solutions and so on. But we never ended up actually using this database to manage problems and solutions, perhaps because the entry bar for hooking it up to other code was too high. Maybe next year will be different.</p>]]></content><author><name>mniip</name></author><summary type="html"><![CDATA[The International Conference on Functional Programming holds an annual programming contest to attract attention to applications of functional programming. This isn’t your average competitive programming contest. The problems are usually designed in some way around functional programming and programming language theory concepts, rather than raw number crunching. More often than not, they are also NP-complete (or worse), and it isn’t expected that anyone comes up with a perfect solution. A typical approach involves some amalgamation of slow exact algorithms, fast approximate algorithms, and even solutions by hand. It is a 72-hour marathon of coming up with the best heuristics to an unsolvable problem. I participated in the contest in a 2-person team (powder: @mniip, @savask), using primarily Haskell. Here’s how it went.]]></summary></entry><entry><title type="html">Bulk Operations with Traversals, Now Composed</title><link href="https://mniip.com/posts/2024-06-20-bulk-operations-now-composed" rel="alternate" type="text/html" title="Bulk Operations with Traversals, Now Composed" /><published>2024-06-20T00:00:00+00:00</published><updated>2024-06-20T00:00:00+00:00</updated><id>https://mniip.com/posts/bulk-operations-now-composed</id><content type="html" xml:base="https://mniip.com/posts/2024-06-20-bulk-operations-now-composed"><![CDATA[<ul id="markdown-toc">
  <li><a href="#recap" id="markdown-toc-recap">Recap</a></li>
  <li><a href="#sequencing" id="markdown-toc-sequencing">Sequencing</a></li>
  <li><a href="#nesting" id="markdown-toc-nesting">Nesting</a></li>
  <li><a href="#zipping" id="markdown-toc-zipping">Zipping</a></li>
  <li><a href="#scalability" id="markdown-toc-scalability">Scalability</a></li>
  <li><a href="#abstraction" id="markdown-toc-abstraction">Abstraction</a></li>
  <li><a href="#another-take-at-nesting" id="markdown-toc-another-take-at-nesting">Another Take at Nesting</a></li>
  <li><a href="#another-take-at-zipping" id="markdown-toc-another-take-at-zipping">Another Take at Zipping</a></li>
  <li><a href="#arrows" id="markdown-toc-arrows">Arrows</a></li>
  <li><a href="#another-take-at-scalability" id="markdown-toc-another-take-at-scalability">Another Take at Scalability</a></li>
  <li><a href="#the-profunctors-package" id="markdown-toc-the-profunctors-package">The <code class="language-plaintext highlighter-rouge">profunctors</code> Package</a></li>
  <li><a href="#appendix" id="markdown-toc-appendix">Appendix</a></li>
</ul>

<h2 id="recap">Recap</h2>

<p><a href="2023-10-24-organizing-bulk-operations-with-traversals">As I’ve written previously</a>, if you have an operation</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">process</span> <span class="o">::</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="kt">B</span>
</code></pre></div></div>
<p>and you need to do this operation in bulk, i.e. turn multiple <code class="language-plaintext highlighter-rouge">A</code>’s into multiple <code class="language-plaintext highlighter-rouge">B</code>’s, then it’s a good idea to represent it as</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processBulk</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">B</span><span class="p">)</span>
</code></pre></div></div>

<p>This type describes an invariant, that the <code class="language-plaintext highlighter-rouge">B</code>’s returned are in a one-to-one correspondence with the input <code class="language-plaintext highlighter-rouge">A</code>’s, and forces us to eagerly check/enforce this invariant. As we’ll see, this actually also makes such bulk operations <em>composable</em>.
<!-- more --></p>

<h2 id="sequencing">Sequencing</h2>

<p>Throughout this post we’ll use a running example of orchestrating bulk select queries to a database with the following toy schema:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────┐               ┌──────┐               ┌────────┐
│Users│──[1-to-many]──│Orders│──[1-to-many]──│Products│
└─────┘               └──────┘               └────────┘
                         │              ┌────────┐
                         └─[1-to-many]──│Payments│
                                        └────────┘
</code></pre></div></div>

<p>If we have two bulk processes that we need to pipe one after another, then it’s no surprise that we can just compose them with <code class="language-plaintext highlighter-rouge">&gt;=&gt;</code>:</p>

<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">orderIDByPaymentID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">PaymentID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">OrderID</span><span class="p">)</span>

<span class="n">userIDByOrderID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">UserID</span><span class="p">)</span>

<span class="n">userIDByPaymentID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">PaymentID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">UserID</span><span class="p">)</span>
<span class="n">userIDByPaymentID</span>
  <span class="o">=</span> <span class="n">orderIDByPaymentID</span> <span class="o">&gt;=&gt;</span> <span class="n">userIDByOrderID</span>
</code></pre></div></div>

<h2 id="nesting">Nesting</h2>

<p>Suppose for some reason we’ve decided to separate the function that selects product ID’s that belong to an order, and the function that selects the actual data of the product by its ID.</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">productIDsByOrderID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">[</span><span class="kt">ProductID</span><span class="p">])</span>
  <span class="c1">-- ^ The nested [] is to reflect the multiplicity relationship:</span>
  <span class="c1">-- One order ID maps to 0 or more product ID's</span>

<span class="n">productDataByID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">ProductID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">ProductData</span><span class="p">)</span>

<span class="n">productDatasByOrderID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">])</span>
<span class="n">productDatasByOrderID</span> <span class="o">=</span> <span class="o">???</span>
</code></pre></div></div>

<p>How do we compose the two functions? We can start with <code class="language-plaintext highlighter-rouge">productIDsByOrderID &gt;=&gt; ???</code> but then the resulting <code class="language-plaintext highlighter-rouge">???</code> has expected type <code class="language-plaintext highlighter-rouge">t [ProductID] -&gt; IO (t [ProductData])</code>, which is not what we’ve got.</p>

<p>Note that the choice of <code class="language-plaintext highlighter-rouge">t</code> we make when calling <code class="language-plaintext highlighter-rouge">productDataByID</code> doesn’t have to match the <code class="language-plaintext highlighter-rouge">t</code> we’re given as implementors of <code class="language-plaintext highlighter-rouge">productDatasByOrderID</code>. Instead what we can do is use <code class="language-plaintext highlighter-rouge">productDataByID</code> at type <code class="language-plaintext highlighter-rouge">Compose t []</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">productDataByID</span> <span class="o">@</span><span class="p">(</span><span class="kt">Compose</span> <span class="n">t</span> <span class="kt">[]</span><span class="p">)</span>
  <span class="o">::</span> <span class="kt">Compose</span> <span class="n">t</span> <span class="kt">[]</span> <span class="kt">ProductID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">t</span> <span class="kt">[]</span> <span class="kt">ProductData</span><span class="p">)</span>
</code></pre></div></div>
<p>With some newtype wrapping/unwrapping, we get:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fmap</span> <span class="n">getCompose</span>
  <span class="o">.</span> <span class="n">productDataByID</span>
  <span class="o">.</span> <span class="kt">Compose</span> <span class="o">@</span><span class="n">t</span> <span class="o">@</span><span class="kt">[]</span>
  <span class="o">::</span> <span class="n">t</span> <span class="p">[</span><span class="kt">ProductID</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">])</span>
</code></pre></div></div>
<p>i.e. exactly what we want. Putting the puzzle pieces together, we can implement the function:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">productDatasByOrderID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">])</span>
<span class="n">productDatasByOrderID</span> <span class="n">orderIDs</span> <span class="o">=</span> <span class="kr">do</span>
  <span class="p">(</span><span class="n">productIDs</span> <span class="o">::</span> <span class="n">t</span> <span class="p">[</span><span class="kt">ProductID</span><span class="p">])</span> <span class="o">&lt;-</span> <span class="n">productIDsByOrderID</span> <span class="n">orderIDs</span>
  <span class="p">(</span><span class="n">productDatas</span> <span class="o">::</span> <span class="kt">Compose</span> <span class="n">t</span> <span class="kt">[]</span> <span class="kt">ProductData</span><span class="p">)</span> <span class="o">&lt;-</span>
    <span class="n">productDataByID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">productIDs</span><span class="p">)</span>
  <span class="n">pure</span> <span class="o">$</span> <span class="n">getCompose</span> <span class="n">productDatas</span>
</code></pre></div></div>
<p>This possibility is due to there existing an <code class="language-plaintext highlighter-rouge">instance (Traversable f, Traversable g) =&gt; Traversable (Compose f g)</code>.</p>

<h2 id="zipping">Zipping</h2>

<p>Suppose we want to select the data of an order by ID, and also the related products:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">orderDataByID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">OrderData</span><span class="p">)</span>

<span class="n">productDatasByOrderID</span> <span class="c1">-- implemented up above</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">])</span>

<span class="n">orderWithProductsByID</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">OrderID</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">]))</span>
<span class="n">orderWithProductsByID</span> <span class="o">=</span> <span class="o">???</span>
</code></pre></div></div>
<p>We could start with taking the collection of order IDs and feeding them to the two functions like so:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">orderWithProductsByID</span> <span class="n">orderIDs</span> <span class="o">=</span> <span class="kr">do</span>
  <span class="p">(</span><span class="n">orderDatas</span> <span class="o">::</span> <span class="n">t</span> <span class="kt">OrderData</span><span class="p">)</span> <span class="o">&lt;-</span> <span class="n">orderDataByID</span> <span class="n">orderIDs</span>
  <span class="p">(</span><span class="n">productDatas</span> <span class="o">::</span> <span class="n">t</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">])</span> <span class="o">&lt;-</span> <span class="n">productDatasByOrderID</span> <span class="n">orderIDs</span>
  <span class="o">???</span>
</code></pre></div></div>
<p>but then we’re left with a puzzle of how to zip together two <code class="language-plaintext highlighter-rouge">t</code> structures. However the only thing we know about <code class="language-plaintext highlighter-rouge">t</code> is that it is <code class="language-plaintext highlighter-rouge">Traversable</code>, and that is <a href="2024-01-01-semialign-intuition">not actually enough</a> to zip arbitrary structures together.</p>

<p>We may note that they’re not actually arbitrary structures, as each operation here preserves the shape, and thus <code class="language-plaintext highlighter-rouge">orderDatas</code> and <code class="language-plaintext highlighter-rouge">productDatas</code> must have the same shape and thus we’re morally entitled to zipping them. You may be reaching for <code class="language-plaintext highlighter-rouge">mapAccumL</code> to unsafely zip them, or for a <code class="language-plaintext highlighter-rouge">Bazaar</code> to represent them as length-indexed vectors, but there’s a much more safe and direct alternative to this.</p>

<p>We’re going to get <code class="language-plaintext highlighter-rouge">productDatasByOrderID</code> to do the zipping for us, and we’re going to do it by passing <code class="language-plaintext highlighter-rouge">OrderData</code> through it, and we’re going to do it for free, <em>without changing <code class="language-plaintext highlighter-rouge">productDatasByOrderID</code></em>.</p>

<p>The trick is to tuck <code class="language-plaintext highlighter-rouge">OrderData</code> into the <code class="language-plaintext highlighter-rouge">t</code>. Just like we represented <code class="language-plaintext highlighter-rouge">t [X]</code> as <code class="language-plaintext highlighter-rouge">Compose t [] X</code>, we can represent <code class="language-plaintext highlighter-rouge">t (X, Y)</code> as <code class="language-plaintext highlighter-rouge">Compose t ((,) X) Y</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fmap</span> <span class="n">getCompose</span>
  <span class="o">.</span> <span class="n">productDatasByOrderID</span>
  <span class="o">.</span> <span class="kt">Compose</span> <span class="o">@</span><span class="n">t</span> <span class="o">@</span><span class="p">((,)</span> <span class="kt">OrderData</span><span class="p">)</span>
  <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="kt">OrderID</span><span class="p">)</span>
  <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">]))</span>
</code></pre></div></div>

<p>Alright, but how do we get <code class="language-plaintext highlighter-rouge">t (OrderData, OrderID)</code> if what we have is <code class="language-plaintext highlighter-rouge">t OrderData</code> and <code class="language-plaintext highlighter-rouge">t OrderID</code>? We can do the exact same trick again: we thread (a copy of) <code class="language-plaintext highlighter-rouge">OrderID</code> through <code class="language-plaintext highlighter-rouge">orderDataByID</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fmap</span> <span class="n">getCompose</span>
  <span class="o">.</span> <span class="n">orderDataByID</span>
  <span class="o">.</span> <span class="kt">Compose</span> <span class="o">@</span><span class="n">t</span> <span class="o">@</span><span class="p">((,)</span> <span class="kt">OrderID</span><span class="p">)</span>
  <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">OrderID</span><span class="p">,</span> <span class="kt">OrderID</span><span class="p">)</span>
  <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">OrderID</span><span class="p">,</span> <span class="kt">OrderData</span><span class="p">))</span>
</code></pre></div></div>
<p>With a little glue we can put the puzzle pieces together:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">orderWithProductsByID</span> <span class="n">orderIDs</span> <span class="o">=</span> <span class="kr">do</span>
  <span class="kr">let</span>
    <span class="n">dupedIDs</span> <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">OrderID</span><span class="p">,</span> <span class="kt">OrderID</span><span class="p">)</span>
    <span class="n">dupedIDs</span> <span class="o">=</span> <span class="n">orderIDs</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
  <span class="kt">Compose</span> <span class="p">(</span><span class="n">idsAndOrderDatas</span> <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">OrderID</span><span class="p">,</span> <span class="kt">OrderData</span><span class="p">))</span> <span class="o">&lt;-</span>
    <span class="n">orderDataByID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">dupedIDs</span><span class="p">)</span>
  <span class="kr">let</span>
    <span class="n">orderDatasAndIDs</span> <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="kt">OrderID</span><span class="p">)</span>
    <span class="n">orderDatasAndIDs</span> <span class="o">=</span> <span class="n">idsAndOrderDatas</span> <span class="o">&lt;&amp;&gt;</span> <span class="n">swap</span>
  <span class="kt">Compose</span> <span class="p">(</span><span class="n">ordersAndProducts</span> <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">]))</span> <span class="o">&lt;-</span>
    <span class="n">productDatasByOrderID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">orderDatasAndIDs</span><span class="p">)</span>
  <span class="n">pure</span> <span class="n">ordersAndProducts</span>
</code></pre></div></div>

<h2 id="scalability">Scalability</h2>

<p>Equipped with these tricks we fear no query:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">usersEverything</span>
  <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span>
  <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">UserID</span>
  <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">UserData</span><span class="p">,</span> <span class="p">[(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">],</span> <span class="p">[</span><span class="kt">PaymentData</span><span class="p">])]))</span>
<span class="n">usersEverything</span> <span class="n">userIDs</span> <span class="o">=</span> <span class="kr">do</span>
  <span class="kr">let</span> <span class="n">uiui</span> <span class="o">=</span> <span class="n">userIDs</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
  <span class="kt">Compose</span> <span class="n">uiud</span> <span class="o">&lt;-</span> <span class="n">userDataByID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">uiui</span><span class="p">)</span>
  <span class="kr">let</span> <span class="n">udui</span> <span class="o">=</span> <span class="n">uiud</span> <span class="o">&lt;&amp;&gt;</span> <span class="n">swap</span>
  <span class="kt">Compose</span> <span class="n">udoi</span> <span class="o">&lt;-</span> <span class="n">orderIDsByUserID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udui</span><span class="p">)</span>
  <span class="kr">let</span> <span class="n">udoioi</span> <span class="o">=</span> <span class="kt">Compose</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udoi</span><span class="p">)</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
  <span class="kt">Compose</span> <span class="n">udoiod</span> <span class="o">&lt;-</span> <span class="n">orderDataByID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udoioi</span><span class="p">)</span>
  <span class="kr">let</span> <span class="n">udodoioi</span> <span class="o">=</span> <span class="kt">Compose</span> <span class="p">(</span><span class="n">udoiod</span> <span class="o">&lt;&amp;&gt;</span> <span class="n">swap</span><span class="p">)</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
  <span class="kt">Compose</span> <span class="n">udodoipi</span> <span class="o">&lt;-</span> <span class="n">productIDsByOrderID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udodoioi</span><span class="p">)</span>
  <span class="kt">Compose</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udodoipd</span><span class="p">)</span> <span class="o">&lt;-</span> <span class="n">productDataByID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udodoipi</span><span class="p">))</span>
  <span class="kr">let</span> <span class="n">udodpdoi</span> <span class="o">=</span> <span class="kt">Compose</span> <span class="p">(</span><span class="n">udodoipd</span> <span class="o">&lt;&amp;&gt;</span> <span class="n">swap</span><span class="p">)</span>
  <span class="n">udodpdyi</span> <span class="o">&lt;-</span> <span class="n">paymentIDsByOrderID</span> <span class="n">udodpdoi</span>
  <span class="n">udodpdyd</span> <span class="o">&lt;-</span> <span class="n">paymentDataByID</span> <span class="p">(</span><span class="kt">Compose</span> <span class="n">udodpdyi</span><span class="p">)</span>
  <span class="n">pure</span> <span class="o">$</span> <span class="n">getCompose</span> <span class="o">$</span> <span class="n">getCompose</span> <span class="o">$</span> <span class="n">fmap</span> <span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">))</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">))</span>
    <span class="o">$</span> <span class="n">getCompose</span> <span class="o">$</span> <span class="n">getCompose</span> <span class="o">$</span> <span class="n">getCompose</span> <span class="n">udodpdyd</span>
</code></pre></div></div>
<p>Actually maybe I fear that query. Can you keep track of the types, cause I for sure cannot. What’s the type of <code class="language-plaintext highlighter-rouge">udodpdyd</code>?</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Compose</span>
  <span class="p">(</span><span class="kt">Compose</span>
    <span class="p">(</span><span class="kt">Compose</span>
      <span class="p">(</span><span class="kt">Compose</span>
        <span class="p">(</span><span class="kt">Compose</span>
          <span class="n">t</span>
          <span class="p">((,)</span> <span class="kt">UserData</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="kt">[]</span>
      <span class="p">)</span>
      <span class="p">((,)</span> <span class="kt">OrderData</span><span class="p">)</span>
    <span class="p">)</span>
    <span class="p">((,)</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">])</span>
  <span class="p">)</span>
  <span class="kt">[]</span>
  <span class="kt">PaymentData</span>
</code></pre></div></div>
<p>There’s got to be a better way.</p>

<h2 id="abstraction">Abstraction</h2>

<p>Up to this point we’ve been treating the traversable approach as a “design pattern”. Haskell’s rich type system actually allows us to precisely capture the nature of this pattern and abstract it as a type:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">newtype</span> <span class="kt">BulkFunction</span> <span class="n">a</span> <span class="n">b</span>
  <span class="o">=</span> <span class="kt">BulkFunction</span> <span class="p">(</span><span class="n">forall</span> <span class="n">t</span><span class="o">.</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="n">b</span><span class="p">))</span>

<span class="n">productDataByID</span> <span class="o">::</span> <span class="kt">BulkFunction</span> <span class="kt">ProductID</span> <span class="kt">ProductData</span>
</code></pre></div></div>

<p>Now we can also abstract the compositional glue we’ve used into well-defined functions. Sequencing gets the simple type:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">BulkFunction</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="kt">BulkFunction</span> <span class="n">y</span> <span class="n">z</span> <span class="o">-&gt;</span> <span class="kt">BulkFunction</span> <span class="n">x</span> <span class="n">z</span>
</code></pre></div></div>
<p>Actually <a href="https://hackage.haskell.org/package/base-4.20.0.1/docs/Control-Category.html">the base library has a typeclass</a> for this kind of operation, and having made a newtype, we can write an instance:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Category</span> <span class="p">(</span><span class="n">q</span> <span class="o">::</span> <span class="kt">Type</span> <span class="o">-&gt;</span> <span class="kt">Type</span> <span class="o">-&gt;</span> <span class="kt">Type</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">id</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">x</span>
  <span class="p">(</span><span class="o">.</span><span class="p">)</span> <span class="o">::</span> <span class="n">q</span> <span class="n">y</span> <span class="n">z</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">z</span>

<span class="kr">instance</span> <span class="kt">Category</span> <span class="kt">BulkFunction</span> <span class="kr">where</span>
  <span class="n">id</span> <span class="o">=</span> <span class="kt">BulkFunction</span> <span class="n">pure</span>
  <span class="kt">BulkFunction</span> <span class="n">f</span> <span class="o">.</span> <span class="kt">BulkFunction</span> <span class="n">g</span> <span class="o">=</span> <span class="kt">BulkFunction</span> <span class="p">(</span><span class="n">f</span> <span class="o">&lt;=&lt;</span> <span class="n">g</span><span class="p">)</span>
</code></pre></div></div>
<p>Actually before we go too far, note that there is already <a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/src/Control.Arrow.html#line-196">another instance of <code class="language-plaintext highlighter-rouge">Category</code></a>, whose <code class="language-plaintext highlighter-rouge">id</code> is <code class="language-plaintext highlighter-rouge">pure</code> and whose <code class="language-plaintext highlighter-rouge">.</code> is <code class="language-plaintext highlighter-rouge">&lt;=&lt;</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">newtype</span> <span class="kt">Kleisli</span> <span class="n">m</span> <span class="n">a</span> <span class="n">b</span> <span class="o">=</span> <span class="kt">Kleisli</span> <span class="p">(</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="n">m</span> <span class="n">b</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">Monad</span> <span class="n">m</span> <span class="o">=&gt;</span> <span class="kt">Category</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="n">m</span><span class="p">)</span>
</code></pre></div></div>
<p>It makes sense to abstract out the <code class="language-plaintext highlighter-rouge">_ -&gt; IO _</code> part as a parameter, and delegate to its instance of <code class="language-plaintext highlighter-rouge">Category</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">newtype</span> <span class="kt">Bulk</span> <span class="n">p</span> <span class="n">a</span> <span class="n">b</span> <span class="o">=</span> <span class="kt">Bulk</span>
  <span class="p">{</span> <span class="n">runBulk</span> <span class="o">::</span> <span class="n">forall</span> <span class="n">t</span><span class="o">.</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">p</span> <span class="p">(</span><span class="n">t</span> <span class="n">a</span><span class="p">)</span> <span class="p">(</span><span class="n">t</span> <span class="n">b</span><span class="p">)</span> <span class="p">}</span>

<span class="kr">instance</span> <span class="kt">Category</span> <span class="n">p</span> <span class="o">=&gt;</span> <span class="kt">Category</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">p</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">id</span> <span class="o">=</span> <span class="kt">Bulk</span> <span class="n">id</span>
  <span class="kt">Bulk</span> <span class="n">f</span> <span class="o">.</span> <span class="kt">Bulk</span> <span class="n">g</span> <span class="o">=</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="n">f</span> <span class="o">.</span> <span class="n">g</span><span class="p">)</span>

<span class="n">productDataByID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">ProductID</span> <span class="kt">ProductData</span>
</code></pre></div></div>

<p>We’re also going to need to be able to map over the <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">b</code> parameters, this is achieved by making it an instance of <code class="language-plaintext highlighter-rouge">Profunctor</code>, from the <a href="https://hackage.haskell.org/package/profunctors">profunctors</a> library:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Profunctor</span> <span class="p">(</span><span class="n">p</span> <span class="o">::</span> <span class="kt">Type</span> <span class="o">-&gt;</span> <span class="kt">Type</span> <span class="o">-&gt;</span> <span class="kt">Type</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">dimap</span> <span class="o">::</span> <span class="p">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">y</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">z</span> <span class="o">-&gt;</span> <span class="n">w</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">p</span> <span class="n">y</span> <span class="n">z</span> <span class="o">-&gt;</span> <span class="n">p</span> <span class="n">x</span> <span class="n">w</span>

<span class="kr">instance</span> <span class="kt">Profunctor</span> <span class="n">p</span> <span class="o">=&gt;</span> <span class="kt">Profunctor</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">p</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">dimap</span> <span class="n">f</span> <span class="n">g</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">h</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="n">dimap</span> <span class="p">(</span><span class="n">fmap</span> <span class="n">f</span><span class="p">)</span> <span class="p">(</span><span class="n">fmap</span> <span class="n">g</span><span class="p">)</span> <span class="n">h</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="another-take-at-nesting">Another Take at Nesting</h2>

<p>Next up we have a notion of nesting: we were able to “lift” <code class="language-plaintext highlighter-rouge">productDataByID</code> like so:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">productDataByID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">ProductID</span> <span class="kt">ProductData</span>

<span class="n">productDatasByIDs</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="p">[</span><span class="kt">ProductID</span><span class="p">]</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">]</span>
<span class="n">productDatasByIDs</span> <span class="o">=</span> <span class="kt">Bulk</span>
  <span class="p">(</span><span class="n">dimap</span> <span class="kt">Compose</span> <span class="n">getCompose</span> <span class="o">$</span> <span class="n">runBulk</span> <span class="n">productDataByID</span><span class="p">)</span>
</code></pre></div></div>
<p>Generalizing this, we have:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">liftList</span> <span class="o">::</span> <span class="kt">Profunctor</span> <span class="n">p</span> <span class="o">=&gt;</span> <span class="kt">Bulk</span> <span class="n">p</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="kt">Bulk</span> <span class="n">p</span> <span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="p">[</span><span class="n">y</span><span class="p">]</span>
</code></pre></div></div>
<p>In fact, generalizing even further, we arrive at:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">liftContainer</span>
  <span class="o">::</span> <span class="p">(</span><span class="kt">Profunctor</span> <span class="n">p</span><span class="p">,</span> <span class="kt">Traversable</span> <span class="n">t</span><span class="p">)</span>
  <span class="o">=&gt;</span> <span class="kt">Bulk</span> <span class="n">p</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="kt">Bulk</span> <span class="n">p</span> <span class="p">(</span><span class="n">t</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="n">t</span> <span class="n">y</span><span class="p">)</span>
<span class="n">liftContainer</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">f</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="n">dimap</span> <span class="kt">Compose</span> <span class="n">getCompose</span> <span class="n">f</span><span class="p">)</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">profunctors</code> library has a typeclass for profunctors that support this operation:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Traversing</span> <span class="n">q</span> <span class="kr">where</span>
  <span class="n">traverse'</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="p">(</span><span class="n">t</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="n">t</span> <span class="n">y</span><span class="p">)</span>

<span class="kr">instance</span> <span class="kt">Profunctor</span> <span class="n">p</span> <span class="o">=&gt;</span> <span class="kt">Traversing</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">p</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">traverse'</span> <span class="o">=</span> <span class="n">liftContainer</span>
</code></pre></div></div>

<h2 id="another-take-at-zipping">Another Take at Zipping</h2>

<p>The trick of tucking/threading data into the traversable is an example of something called profunctor strength:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Strong</span> <span class="n">q</span> <span class="kr">where</span>
  <span class="n">first'</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
  <span class="n">second'</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="p">(</span><span class="n">z</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="n">z</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>

<span class="kr">instance</span> <span class="kt">Profunctor</span> <span class="n">p</span> <span class="o">=&gt;</span> <span class="kt">Strong</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">p</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">second'</span> <span class="o">=</span> <span class="n">traverse'</span>
  <span class="c1">-- ^ slick definition also leveraging `Compose t ((,) c)`</span>
  <span class="n">first'</span> <span class="n">f</span> <span class="o">=</span> <span class="n">dimap</span> <span class="n">swap</span> <span class="n">swap</span> <span class="o">$</span> <span class="n">second'</span> <span class="n">f</span>
</code></pre></div></div>

<p>With these two functions we can define a “fan-out” operation:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fanout</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Category</span> <span class="n">q</span><span class="p">,</span> <span class="kt">Strong</span> <span class="n">q</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">z</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
<span class="n">fanout</span> <span class="n">f</span> <span class="n">g</span> <span class="o">=</span> <span class="n">dimap</span> <span class="p">(</span><span class="nf">\</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">x</span><span class="p">))</span> <span class="n">id</span>
  <span class="p">(</span><span class="n">second'</span> <span class="n">g</span> <span class="o">.</span> <span class="n">first'</span> <span class="n">f</span><span class="p">)</span>
</code></pre></div></div>
<p>which can in particular be specialized to <code class="language-plaintext highlighter-rouge">q ~ Bulk p</code>, whence it supplies the same input to two processes, and “zips” their results.</p>

<h2 id="arrows">Arrows</h2>

<p>What we’ve just defined looks very similar to the <code class="language-plaintext highlighter-rouge">&amp;&amp;&amp;</code> operator from <a href="https://hackage.haskell.org/package/base-4.14.1.0/docs/Control-Arrow.html#v:-38--38--38-">Control.Arrow</a>, both in type signature and in implementation:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Category</span> <span class="n">q</span> <span class="o">=&gt;</span> <span class="kt">Arrow</span> <span class="n">q</span> <span class="kr">where</span>
  <span class="n">arr</span> <span class="o">::</span> <span class="p">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">y</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span>
  <span class="n">first</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
  <span class="n">second</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="p">(</span><span class="n">z</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="n">z</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>

  <span class="p">(</span><span class="o">***</span><span class="p">)</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">z</span> <span class="n">w</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">w</span><span class="p">)</span>
  <span class="n">f</span> <span class="o">***</span> <span class="n">g</span> <span class="o">=</span> <span class="n">second</span> <span class="n">g</span> <span class="o">.</span> <span class="n">first</span> <span class="n">f</span>
  <span class="p">(</span><span class="o">&amp;&amp;&amp;</span><span class="p">)</span> <span class="o">::</span> <span class="n">q</span> <span class="n">x</span> <span class="n">y</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="n">z</span> <span class="o">-&gt;</span> <span class="n">q</span> <span class="n">x</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
  <span class="n">f</span> <span class="o">&amp;&amp;&amp;</span> <span class="n">g</span> <span class="o">=</span> <span class="p">(</span><span class="n">f</span> <span class="o">***</span> <span class="n">g</span><span class="p">)</span> <span class="o">.</span> <span class="n">arr</span> <span class="p">(</span><span class="nf">\</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">x</span><span class="p">))</span>
</code></pre></div></div>
<p>And indeed it is. <code class="language-plaintext highlighter-rouge">base</code> cannot depend on <code class="language-plaintext highlighter-rouge">profunctors</code>, but morally <code class="language-plaintext highlighter-rouge">Arrow</code> is equivalent to <code class="language-plaintext highlighter-rouge">Strong</code>+<code class="language-plaintext highlighter-rouge">Category</code>, because the typeclasses can be inter-defined like so:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Arrow</span> <span class="n">q</span> <span class="o">=&gt;</span> <span class="kt">Profunctor</span> <span class="n">q</span> <span class="kr">where</span>
  <span class="n">dimap</span> <span class="n">f</span> <span class="n">g</span> <span class="n">h</span> <span class="o">=</span> <span class="n">arr</span> <span class="n">g</span> <span class="o">.</span> <span class="n">h</span> <span class="o">.</span> <span class="n">arr</span> <span class="n">f</span>
<span class="kr">instance</span> <span class="kt">Arrow</span> <span class="n">q</span> <span class="o">=&gt;</span> <span class="kt">Strong</span> <span class="n">q</span> <span class="kr">where</span>
  <span class="n">first'</span> <span class="o">=</span> <span class="n">first</span>
  <span class="n">second'</span> <span class="o">=</span> <span class="n">second</span>

<span class="kr">instance</span> <span class="p">(</span><span class="kt">Strong</span> <span class="n">q</span><span class="p">,</span> <span class="kt">Category</span> <span class="n">q</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kt">Arrow</span> <span class="n">q</span> <span class="kr">where</span>
  <span class="n">arr</span> <span class="n">f</span> <span class="o">=</span> <span class="n">dimap</span> <span class="n">f</span> <span class="n">id</span> <span class="n">id</span>
  <span class="n">first</span> <span class="o">=</span> <span class="n">first'</span>
  <span class="n">second</span> <span class="o">=</span> <span class="n">second'</span>
</code></pre></div></div>
<p>Under this identification we see that <code class="language-plaintext highlighter-rouge">fanout</code> is actually equal to <code class="language-plaintext highlighter-rouge">&amp;&amp;&amp;</code>, and the following is sufficient to define <code class="language-plaintext highlighter-rouge">Arrow</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="p">(</span><span class="kt">Profunctor</span> <span class="n">p</span><span class="p">,</span> <span class="kt">Category</span> <span class="n">p</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kt">Arrow</span> <span class="p">(</span><span class="kt">Bulk</span> <span class="n">p</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">arr</span> <span class="n">f</span> <span class="o">=</span> <span class="n">dimap</span> <span class="n">f</span> <span class="n">id</span> <span class="n">id</span>
  <span class="n">first</span> <span class="o">=</span> <span class="n">first'</span>
  <span class="n">second</span> <span class="o">=</span> <span class="n">second'</span>
</code></pre></div></div>
<p>Note that if we instead defined <code class="language-plaintext highlighter-rouge">arr f = Bulk (arr (fmap f))</code>, this would unnecessarily require <code class="language-plaintext highlighter-rouge">Strong p</code>.</p>

<h2 id="another-take-at-scalability">Another Take at Scalability</h2>

<p>Equipped with <code class="language-plaintext highlighter-rouge">Arrow</code>, <code class="language-plaintext highlighter-rouge">Traversing</code>, etc typeclass methods as compositional glue combinators, we can take another stab at defining a large query out of smaller pieces:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">userDataByID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">UserID</span> <span class="kt">UserData</span>
<span class="n">orderDataByID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">OrderID</span> <span class="kt">OrderData</span>
<span class="n">productDataByID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">ProductID</span> <span class="kt">ProductData</span>
<span class="n">paymentDataByID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">PaymentID</span> <span class="kt">PaymentData</span>
<span class="n">orderIDsByUserID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">UserID</span> <span class="p">[</span><span class="kt">OrderID</span><span class="p">]</span>
<span class="n">productIDsByOrderID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">OrderID</span> <span class="p">[</span><span class="kt">ProductID</span><span class="p">]</span>
<span class="n">paymentIDsByOrderID</span> <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span> <span class="kt">OrderID</span> <span class="p">[</span><span class="kt">PaymentID</span><span class="p">]</span>

<span class="n">usersEverything</span>
  <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span>
    <span class="kt">UserID</span>
    <span class="p">(</span><span class="kt">UserData</span><span class="p">,</span> <span class="p">[(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">],</span> <span class="p">[</span><span class="kt">PaymentData</span><span class="p">])])</span>
<span class="n">usersEverything</span>
  <span class="o">=</span> <span class="n">userDataByID</span>
    <span class="o">&amp;&amp;&amp;</span> <span class="p">(</span><span class="n">traverse'</span> <span class="n">orderEverything</span> <span class="o">.</span> <span class="n">orderIDsByUserID</span><span class="p">)</span>
  <span class="kr">where</span>
    <span class="n">orderEverything</span> <span class="o">=</span> <span class="n">dimap</span> <span class="n">id</span> <span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">))</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">))</span>
      <span class="o">$</span> <span class="n">orderDataByID</span>
        <span class="o">&amp;&amp;&amp;</span> <span class="p">(</span><span class="n">traverse'</span> <span class="n">productDataByID</span> <span class="o">.</span> <span class="n">productIDsByOrderID</span><span class="p">)</span>
        <span class="o">&amp;&amp;&amp;</span> <span class="p">(</span><span class="n">traverse'</span> <span class="n">paymentDataByID</span> <span class="o">.</span> <span class="n">paymentIDsByOrderID</span><span class="p">)</span>
</code></pre></div></div>
<p>Much better. What’s the type of <code class="language-plaintext highlighter-rouge">orderEverything</code>?</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">orderEverything</span>
  <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span>
    <span class="kt">OrderID</span>
    <span class="p">(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">],</span> <span class="p">[</span><span class="kt">PaymentData</span><span class="p">])</span>
</code></pre></div></div>

<p>Now you can compose these bulk processes pretty much the same way as you can compose ordinary functions, except you’re forced to use point-free style, because you can’t give names to intermediate values using lambda binders.</p>

<p>Or can you? With the <code class="language-plaintext highlighter-rouge">Arrow</code> typeclass comes the <code class="language-plaintext highlighter-rouge">Arrows</code> extension which normally gets very little use, but works perfectly here. Just like a <code class="language-plaintext highlighter-rouge">do</code>-block lets you give names to results of monadic actions, a <code class="language-plaintext highlighter-rouge">proc</code> block lets you give names to values flowing in a string diagram, which is then “compiled” down to <code class="language-plaintext highlighter-rouge">Arrow</code> operations.</p>

<p>Here’s what the function looks like using arrow syntax:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">usersEverything</span>
  <span class="o">::</span> <span class="kt">Bulk</span> <span class="p">(</span><span class="kt">Kleisli</span> <span class="kt">IO</span><span class="p">)</span>
    <span class="kt">UserID</span>
    <span class="p">(</span><span class="kt">UserData</span><span class="p">,</span> <span class="p">[(</span><span class="kt">OrderData</span><span class="p">,</span> <span class="p">[</span><span class="kt">ProductData</span><span class="p">],</span> <span class="p">[</span><span class="kt">PaymentData</span><span class="p">])])</span>
<span class="n">usersEverything</span> <span class="o">=</span> <span class="n">proc</span> <span class="n">userID</span> <span class="o">-&gt;</span> <span class="kr">do</span>
  <span class="n">userData</span> <span class="o">&lt;-</span> <span class="n">userDataByID</span> <span class="o">-&lt;</span> <span class="n">userID</span>
  <span class="n">orderIDs</span> <span class="o">&lt;-</span> <span class="n">orderIDsByUserID</span> <span class="o">-&lt;</span> <span class="n">userID</span>
  <span class="n">orders</span> <span class="o">&lt;-</span> <span class="n">traverse'</span> <span class="n">orderEverything</span> <span class="o">-&lt;</span> <span class="n">orderIDs</span>
  <span class="n">returnA</span> <span class="o">-&lt;</span> <span class="p">(</span><span class="n">userData</span><span class="p">,</span> <span class="n">orders</span><span class="p">)</span>
  <span class="kr">where</span>
    <span class="n">orderEverything</span> <span class="o">=</span> <span class="n">proc</span> <span class="n">orderID</span> <span class="o">-&gt;</span> <span class="kr">do</span>
      <span class="n">orderData</span> <span class="o">&lt;-</span> <span class="n">orderDataByID</span> <span class="o">-&lt;</span> <span class="n">orderID</span>
      <span class="n">productIDs</span> <span class="o">&lt;-</span> <span class="n">productIDsByOrderID</span> <span class="o">-&lt;</span> <span class="n">orderID</span>
      <span class="n">paymentIDs</span> <span class="o">&lt;-</span> <span class="n">paymentIDsByOrderID</span> <span class="o">-&lt;</span> <span class="n">orderID</span>
      <span class="n">products</span> <span class="o">&lt;-</span> <span class="n">traverse'</span> <span class="n">productDataByID</span> <span class="o">-&lt;</span> <span class="n">productIDs</span>
      <span class="n">payments</span> <span class="o">&lt;-</span> <span class="n">traverse'</span> <span class="n">paymentDataByID</span> <span class="o">-&lt;</span> <span class="n">paymentIDs</span>
      <span class="n">returnA</span> <span class="o">-&lt;</span> <span class="p">(</span><span class="n">orderData</span><span class="p">,</span> <span class="n">products</span><span class="p">,</span> <span class="n">payments</span><span class="p">)</span>
</code></pre></div></div>
<p>With all intermediate values named, the code is self-documenting, you can add type signatures to the intermediate values, do some (irrefutable) pattern matching, construct return values (normally you’d probably be using a record type instead of this <code class="language-plaintext highlighter-rouge">(,,)</code> triple).</p>

<p>As an exercise to the reader, you can figure out the connection between <a href="https://hackage.haskell.org/package/profunctors-5.6.2/docs/Data-Profunctor-Choice.html#t:Choice"><code class="language-plaintext highlighter-rouge">Choice</code></a> and <a href="https://hackage.haskell.org/package/base-4.20.0.1/docs/Control-Arrow.html#t:ArrowChoice"><code class="language-plaintext highlighter-rouge">ArrowChoice</code></a>, how <code class="language-plaintext highlighter-rouge">traverse'</code> gives you <code class="language-plaintext highlighter-rouge">right'</code> for free, and what that lets you do in terms of arrow syntax and DB queries.</p>

<h2 id="the-profunctors-package">The <code class="language-plaintext highlighter-rouge">profunctors</code> Package</h2>

<p>The <code class="language-plaintext highlighter-rouge">Bulk</code> type actually exists in the <code class="language-plaintext highlighter-rouge">profunctors</code> package as <code class="language-plaintext highlighter-rouge">CofreeTraversing</code>, and there is a <code class="language-plaintext highlighter-rouge">WrappedArrow</code> newtype to evidence that an <code class="language-plaintext highlighter-rouge">Arrow</code> is <code class="language-plaintext highlighter-rouge">Strong</code>, and that an <code class="language-plaintext highlighter-rouge">ArrowChoice</code> is <code class="language-plaintext highlighter-rouge">Choice</code>, but it’s lacking a mechanism to observe the converse, i.e. that <code class="language-plaintext highlighter-rouge">CofreeTraversing</code> can be used with arrow syntax. However it is simple enough to add a blanket newtype wrapper that would translate <code class="language-plaintext highlighter-rouge">Strong</code> into <code class="language-plaintext highlighter-rouge">Arrow</code> and so on.</p>

<h2 id="appendix">Appendix</h2>

<p>A note about query performance: if we’re talking to a real database, the implementation of, say, <code class="language-plaintext highlighter-rouge">userDataByID</code> will have to build something like a <code class="language-plaintext highlighter-rouge">Map</code> or <code class="language-plaintext highlighter-rouge">HashMap</code> of values the DB has returned, in order to build the resulting traversable. <code class="language-plaintext highlighter-rouge">usersEverything</code> is composed out of multiple such functions, and in essence what happens is we send several queries to the DB, and glue their results together using <code class="language-plaintext highlighter-rouge">Map</code> or <code class="language-plaintext highlighter-rouge">HashMap</code>. This is essentially like executing a <code class="language-plaintext highlighter-rouge">JOIN</code> query using a Merge Join or a Hash Join strategy, except that the Merge/Hash Join part has been moved out of the DB’s query planner and into our application. This has the disadvantage of depriving the query planner of the possibility to make an optimal choice based on the exact query and the data. Taking full advantage of the query planner would require making an SQL query builder – definitely possible but a little outside the scope of this post.</p>

<p>Also, beware of long <code class="language-plaintext highlighter-rouge">IN ('x', 'y', ...)</code> lists, instead consider a <a href="https://www.postgresql.org/docs/16/queries-values.html"><code class="language-plaintext highlighter-rouge">VALUES</code> list</a> (which can be <code class="language-plaintext highlighter-rouge">IN</code>‘d, or <code class="language-plaintext highlighter-rouge">JOIN</code>‘ed with).</p>]]></content><author><name>mniip</name></author><summary type="html"><![CDATA[Recap Recap As I’ve written previously, if you have an operation process :: A -&gt; IO B and you need to do this operation in bulk, i.e. turn multiple A’s into multiple B’s, then it’s a good idea to represent it as processBulk :: Traversable t =&gt; t A -&gt; IO (t B) This type describes an invariant, that the B’s returned are in a one-to-one correspondence with the input A’s, and forces us to eagerly check/enforce this invariant. As we’ll see, this actually also makes such bulk operations composable.]]></summary></entry><entry><title type="html">From Semialign to Crosswalk</title><link href="https://mniip.com/posts/2024-01-01-semialign-intuition" rel="alternate" type="text/html" title="From Semialign to Crosswalk" /><published>2024-01-01T00:00:00+00:00</published><updated>2024-01-01T00:00:00+00:00</updated><id>https://mniip.com/posts/semialign-intuition</id><content type="html" xml:base="https://mniip.com/posts/2024-01-01-semialign-intuition"><![CDATA[<ul id="markdown-toc">
  <li><a href="#shapes" id="markdown-toc-shapes">Shapes</a></li>
  <li><a href="#unions" id="markdown-toc-unions">Unions</a></li>
  <li><a href="#empty" id="markdown-toc-empty">Empty</a></li>
  <li><a href="#aside-lattices" id="markdown-toc-aside-lattices">Aside: Lattices</a></li>
  <li><a href="#intersections" id="markdown-toc-intersections">Intersections</a></li>
  <li><a href="#distributivity" id="markdown-toc-distributivity">Distributivity</a></li>
  <li><a href="#full" id="markdown-toc-full">Full</a></li>
  <li><a href="#crosswalk" id="markdown-toc-crosswalk">Crosswalk</a></li>
</ul>

<h2 id="shapes">Shapes</h2>

<p>As explained in the <a href="https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Traversable.html#g:4">official docs for <code class="language-plaintext highlighter-rouge">Traversable</code></a>, a traversable functor can be intuitively decomposed into a “shape” and a list of “elements”:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">=</span> <span class="n">t</span> <span class="nb">()</span>
<span class="n">shape</span> <span class="o">::</span> <span class="kt">Functor</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">Shape</span> <span class="n">t</span>
<span class="n">shape</span> <span class="n">xs</span> <span class="o">=</span> <span class="nb">()</span> <span class="o">&lt;$</span> <span class="n">xs</span>

<span class="n">decompose</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">Shape</span> <span class="n">t</span><span class="p">,</span> <span class="p">[</span><span class="n">a</span><span class="p">])</span>
<span class="n">decompose</span> <span class="n">xs</span> <span class="o">=</span> <span class="p">(</span><span class="n">shape</span> <span class="n">xs</span><span class="p">,</span> <span class="n">toList</span> <span class="n">xs</span><span class="p">)</span>
</code></pre></div></div>

<p>We can even recombine the shape and the elements together, but note that <code class="language-plaintext highlighter-rouge">length (shape xs) = length xs = length (toList xs)</code>, and this becomes a requirement for being able to recombine:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">recombine</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="kt">Shape</span> <span class="n">t</span><span class="p">,</span> <span class="p">[</span><span class="n">a</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">a</span>
<span class="n">recombine</span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">elems</span><span class="p">)</span> <span class="o">=</span>
  <span class="kr">case</span> <span class="n">mapAccumL</span> <span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="nb">()</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">xs</span><span class="p">,</span> <span class="n">x</span><span class="p">))</span> <span class="n">elems</span> <span class="n">s</span> <span class="kr">of</span>
    <span class="p">(</span><span class="kt">[]</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">res</span>
</code></pre></div></div>
<p>In a dependently typed language we could precisely encode this invariant with a dependent sum/dependent pair and a length-indexed vector:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">Decomposition</span> <span class="n">t</span> <span class="n">a</span> <span class="o">=</span> <span class="p">{</span> <span class="n">shape</span> <span class="o">::</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">|</span> <span class="kt">Vector</span> <span class="p">(</span><span class="n">length</span> <span class="n">shape</span><span class="p">)</span> <span class="n">a</span> <span class="p">}</span>
</code></pre></div></div>
<p>In fancy math terms, we have a <em>fibration</em> <code class="language-plaintext highlighter-rouge">shape :: t a -&gt; Shape t</code>, whose <em>total space</em> is <code class="language-plaintext highlighter-rouge">t a</code>, and its <em>base space</em> is <code class="language-plaintext highlighter-rouge">Shape t</code>. Given a specific shape <code class="language-plaintext highlighter-rouge">s :: Shape t</code>, we have the <em>fiber over <code class="language-plaintext highlighter-rouge">s</code></em>: a set of values of type <code class="language-plaintext highlighter-rouge">t a</code> that have shape <code class="language-plaintext highlighter-rouge">s</code>. As the above decomposition shows, this fiber is isomorphic to <code class="language-plaintext highlighter-rouge">Vector (length s) a</code>, i.e. a product of exactly <code class="language-plaintext highlighter-rouge">length s</code> copies of <code class="language-plaintext highlighter-rouge">a</code>.</p>

<p>Examples:</p>
<ul>
  <li>If <code class="language-plaintext highlighter-rouge">t ~ []</code>, we have <code class="language-plaintext highlighter-rouge">Shape []</code> isomorphic to <code class="language-plaintext highlighter-rouge">Natural</code> — the shape of a list is uniquely determined by its length.</li>
  <li>If <code class="language-plaintext highlighter-rouge">t ~ Map k</code>, then <code class="language-plaintext highlighter-rouge">Shape (Map k)</code> is isomorphic to <code class="language-plaintext highlighter-rouge">Set k</code> — the shape of a map is its set of keys. Note that in this case multiple shapes can have the same length.</li>
  <li>If <code class="language-plaintext highlighter-rouge">t ~ Maybe</code>, then there are only two shapes: a shape for <code class="language-plaintext highlighter-rouge">Nothing</code>, of length 0; and a shape for <code class="language-plaintext highlighter-rouge">Just</code>, of length 1. There are no shapes of length 2 or more.</li>
</ul>

<p>The function <code class="language-plaintext highlighter-rouge">length</code> encodes a relationship between shapes and natural numbers. In general it can be arbitrary: given some set of shapes <code class="language-plaintext highlighter-rouge">S</code> and some function <code class="language-plaintext highlighter-rouge">l :: S -&gt; Natural</code>, we have a container:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">C</span> <span class="n">a</span> <span class="o">=</span> <span class="p">{</span> <span class="n">s</span> <span class="o">::</span> <span class="kt">S</span> <span class="o">|</span> <span class="kt">Vector</span> <span class="p">(</span><span class="n">l</span> <span class="n">s</span><span class="p">)</span> <span class="n">a</span> <span class="p">}</span>
</code></pre></div></div>
<p>such that <code class="language-plaintext highlighter-rouge">Shape C</code> is isomorphic to <code class="language-plaintext highlighter-rouge">S</code>, and <code class="language-plaintext highlighter-rouge">length = l</code> (via the aforementioned isomorphism).</p>

<p>For a general traversable functor you know nothing about how its space of shapes is structured, and the <code class="language-plaintext highlighter-rouge">Traversable</code> interface is centered around the notion of <em>preserving</em> the shape: you work on one “container” at a time, and whatever shape comes in, also comes out. To work with multiple containers at once, we need additional structure to talk about how their shapes interact.</p>

<h2 id="unions">Unions</h2>

<p>A problem often occurring in the real world is that of zipping lists together. There’s a function called <code class="language-plaintext highlighter-rouge">Prelude.zip</code> that tries to address this problem, but for lists of uneven length it truncates at the shorter list, and silently ignores the tail of the longer list. For some tasks this is unsuitable, and instead you want to take the longest of the two lists, padding the shorter one in some manner. As far as I can understand, this is the original problem that <a href="https://hackage.haskell.org/package/these"><code class="language-plaintext highlighter-rouge">these</code></a> and later <a href="https://hackage.haskell.org/package/semialign"><code class="language-plaintext highlighter-rouge">semialign</code></a> set out to solve.</p>

<p>There we see things like:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">These</span> <span class="n">a</span> <span class="n">b</span> <span class="o">=</span> <span class="kt">This</span> <span class="n">a</span> <span class="o">|</span> <span class="kt">That</span> <span class="n">b</span> <span class="o">|</span> <span class="kt">These</span> <span class="n">a</span> <span class="n">b</span>

<span class="kr">class</span> <span class="kt">Semialign</span> <span class="n">t</span> <span class="kr">where</span>
  <span class="n">align</span> <span class="o">::</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a</span> <span class="n">b</span><span class="p">)</span>

<span class="kr">instance</span> <span class="kt">Semialign</span> <span class="kt">[]</span>
<span class="kr">instance</span> <span class="kt">Ord</span> <span class="n">k</span> <span class="o">=&gt;</span> <span class="kt">Semialign</span> <span class="p">(</span><span class="kt">Map</span> <span class="n">k</span><span class="p">)</span>
</code></pre></div></div>

<p>It may be tempting to assume that we can simply take the “longer” container, and then combine the elements by padding the “shorter” container. But, say, in case of <code class="language-plaintext highlighter-rouge">Map k</code> that’s not even well defined. For <code class="language-plaintext highlighter-rouge">Map.singleton 'x' 3</code> and <code class="language-plaintext highlighter-rouge">Map.singleton 'y' 2</code>, neither can be said to be “longer” than the other, and what we probably want to get out of combining them is actually <code class="language-plaintext highlighter-rouge">Map.fromList [('x', This 3), ('y', That 2)]</code>.</p>

<p>If we focus on what happens to the <em>shapes</em> of the containers:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">join</span> <span class="o">::</span> <span class="kt">Semialign</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">Shape</span> <span class="n">t</span>
<span class="n">join</span> <span class="n">s</span> <span class="n">u</span> <span class="o">=</span> <span class="nb">()</span> <span class="o">&lt;$</span> <span class="n">align</span> <span class="n">s</span> <span class="n">u</span>
</code></pre></div></div>
<p>we see that we’ve actually made up a binary operation that is:</p>
<ul>
  <li><em>commutative</em>: <code class="language-plaintext highlighter-rouge">join s u = join u s</code>,</li>
  <li><em>associative</em>: <code class="language-plaintext highlighter-rouge">join s (join u v) = join (join s u) v</code>,</li>
  <li>and <em>idempotent</em>: <code class="language-plaintext highlighter-rouge">join s s = s</code>.</li>
</ul>

<p>For <code class="language-plaintext highlighter-rouge">[]</code>, whose shapes are identified with natural numbers, this is the operation of taking the maximum of two numbers. For <code class="language-plaintext highlighter-rouge">Map k</code>, it is the operation of taking the union of two sets.</p>

<p>What’s left is figuring out what happens to the elements. We’ve combined a container of shape <code class="language-plaintext highlighter-rouge">s</code> with <code class="language-plaintext highlighter-rouge">length s</code> elements and a container of shape <code class="language-plaintext highlighter-rouge">u</code> with <code class="language-plaintext highlighter-rouge">length u</code> elements, to obtain a container of shape <code class="language-plaintext highlighter-rouge">join s u</code> with <code class="language-plaintext highlighter-rouge">length (join s u)</code> elements. So how are the input elements related to the output elements? Parametricity forbids us from actually changing the element values, so we are restricted to talking about <em>locations</em> of the elements.</p>

<p>We expect that the shape <code class="language-plaintext highlighter-rouge">join s u</code> has locations that were there in <code class="language-plaintext highlighter-rouge">s</code>, locations that were there in <code class="language-plaintext highlighter-rouge">u</code>, and locations that were there in both; but not locations that weren’t in either. This is enforced by giving <code class="language-plaintext highlighter-rouge">align</code> the following type:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">align</span> <span class="o">::</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a</span> <span class="n">b</span><span class="p">)</span>
</code></pre></div></div>

<p>In addition to the commutativity, associativity, and idempotency properties that can be expressed using just shapes, we have to preserve the structure of the elements. Elements form an ordered list, and <code class="language-plaintext highlighter-rouge">align</code> preserves this structure. As we map locations from <code class="language-plaintext highlighter-rouge">s</code> to <code class="language-plaintext highlighter-rouge">join s u</code>, we don’t lose or duplicate any, and we preserve their order:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">toList</span> <span class="n">xs</span> <span class="o">=</span> <span class="n">mapMaybe</span> <span class="n">here</span> <span class="p">(</span><span class="n">toList</span> <span class="p">(</span><span class="n">align</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">))</span>
  <span class="kr">where</span>
    <span class="n">here</span> <span class="o">::</span> <span class="kt">These</span> <span class="n">a</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="n">a</span>
    <span class="n">here</span> <span class="p">(</span><span class="kt">This</span> <span class="n">x</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Just</span> <span class="n">x</span>
    <span class="n">here</span> <span class="p">(</span><span class="kt">That</span> <span class="kr">_</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Nothing</span>
    <span class="n">here</span> <span class="p">(</span><span class="kt">These</span> <span class="n">x</span> <span class="kr">_</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Just</span> <span class="n">x</span>
</code></pre></div></div>

<p>The attribution of locations done by <code class="language-plaintext highlighter-rouge">align</code> can be demonstrated on a diagram like this:</p>
<figure>
    <img src="/static/img/semialign-intuition/semialign_map_diagram.svg" alt="A diagram demonstrating the attribution of locations when semialign'ing two maps" />
</figure>

<h2 id="empty">Empty</h2>

<p>Where there’s a binary operation, there’s a potential for having units. We want <code class="language-plaintext highlighter-rouge">nil :: Shape t</code> such that <code class="language-plaintext highlighter-rouge">join xs nil = xs</code>. For elements we expect a stronger property: an element of <code class="language-plaintext highlighter-rouge">align xs nil</code> is never attributed to an element of <code class="language-plaintext highlighter-rouge">nil</code>, thus:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">align</span> <span class="n">xs</span> <span class="n">nil</span> <span class="o">=</span> <span class="kt">This</span> <span class="o">&lt;$&gt;</span> <span class="n">xs</span>
</code></pre></div></div>
<p>A way to enforce this using types is to say that actually <code class="language-plaintext highlighter-rouge">nil :: t Void</code>, and then <code class="language-plaintext highlighter-rouge">align xs nil :: t (These a Void)</code>, and <code class="language-plaintext highlighter-rouge">These a Void</code> is forced to be the <code class="language-plaintext highlighter-rouge">This</code> constructor. This implies that <code class="language-plaintext highlighter-rouge">nil</code> has no elements (<code class="language-plaintext highlighter-rouge">length nil = 0</code>).</p>

<p>The <code class="language-plaintext highlighter-rouge">semialign</code> library uses an equivalent formulation:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Semialign</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="kt">Align</span> <span class="n">t</span> <span class="kr">where</span>
  <span class="n">nil</span> <span class="o">::</span> <span class="n">t</span> <span class="n">a</span>
</code></pre></div></div>

<p>The left unit laws (<code class="language-plaintext highlighter-rouge">join nil ys = ys</code>, <code class="language-plaintext highlighter-rouge">align nil ys = That &lt;$&gt; ys</code>) follow from commutativity of <code class="language-plaintext highlighter-rouge">align</code>.</p>

<h2 id="aside-lattices">Aside: Lattices</h2>

<p>A <a href="https://en.wikipedia.org/wiki/Semilattice">semilattice</a> is a structure that can be defined from two different perspectives:</p>
<ul>
  <li>Order-theoretically, it is a partially ordered set with some relation <code class="language-plaintext highlighter-rouge">&lt;=</code>, and it has <em>least upper bounds</em>: for any <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code> there exists an “upper bound” <code class="language-plaintext highlighter-rouge">L</code> (<code class="language-plaintext highlighter-rouge">x &lt;= L</code> and <code class="language-plaintext highlighter-rouge">y &lt;= L</code>) that is “least” (if <code class="language-plaintext highlighter-rouge">x &lt;= M</code> and <code class="language-plaintext highlighter-rouge">y &lt;= M</code> then <code class="language-plaintext highlighter-rouge">L &lt;= M</code>).</li>
  <li>Algebraically, it is a set with a binary operation <code class="language-plaintext highlighter-rouge">join</code> that is commutative, associative, and idempotent.
To connect the two we see that <code class="language-plaintext highlighter-rouge">join x y</code> is the least upper bound of <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code>, and the relation <code class="language-plaintext highlighter-rouge">&lt;=</code> is defined by:
    <div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">&lt;=</span> <span class="n">y</span>  <span class="o">&lt;=&gt;</span>  <span class="n">y</span> <span class="o">=</span> <span class="n">join</span> <span class="n">x</span> <span class="n">y</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>For example, the <code class="language-plaintext highlighter-rouge">max</code> operation on the naturals is connected to the ordinary <code class="language-plaintext highlighter-rouge">&lt;=</code> relation on the naturals. The union of sets is connected to the relation of being a subset.</p>

<p>So partially ordered sets are a “superclass” of semilattices, but to observe this we have to forgo of the <code class="language-plaintext highlighter-rouge">join</code> binary operation and work with <code class="language-plaintext highlighter-rouge">&lt;=</code> instead.</p>

<p>On shapes, <code class="language-plaintext highlighter-rouge">&lt;=</code> is a relation and doesn’t carry any data. With elements, we see that in the case that <code class="language-plaintext highlighter-rouge">s &lt;= u</code>, we have to introduce the data that relates the locations in <code class="language-plaintext highlighter-rouge">s</code> and the locations in <code class="language-plaintext highlighter-rouge">u</code>. We expect that a location in <code class="language-plaintext highlighter-rouge">u</code> is related to 0 or 1 locations in <code class="language-plaintext highlighter-rouge">s</code>, and that the order is preserved. This can be captured as a pair of maps:</p>
<ul>
  <li>An expansion map <code class="language-plaintext highlighter-rouge">Vector (length s) a -&gt; Vector (length u) (Maybe a)</code> that doesn’t drop or duplicate elements, but may skip over some locations in the output (filling them with <code class="language-plaintext highlighter-rouge">Nothing</code>).</li>
  <li>A restriction map <code class="language-plaintext highlighter-rouge">Vector (length u) a -&gt; Vector (length s) a</code> that doesn’t duplicate elements or skip over locations, but may drop elements.</li>
</ul>

<p>In terms of the container type <code class="language-plaintext highlighter-rouge">t</code>, we could assign them types like:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expansion</span> <span class="o">::</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="n">a</span><span class="p">))</span>
<span class="n">restriction</span> <span class="o">::</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="p">(</span><span class="n">t</span> <span class="n">a</span><span class="p">)</span>
</code></pre></div></div>
<p>but these don’t capture nearly enough invariants, which we have to state as additional axioms:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span> <span class="n">expansion</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="kt">Just</span> <span class="n">ys</span> <span class="kr">then</span><span class="o">:</span>
  <span class="n">shape</span> <span class="n">ys</span> <span class="o">=</span> <span class="n">s</span>
<span class="kr">if</span> <span class="n">restriction</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="kt">Just</span> <span class="n">ys</span> <span class="kr">then</span><span class="o">:</span>
  <span class="n">shape</span> <span class="n">ys</span> <span class="o">=</span> <span class="n">s</span>
<span class="n">isJust</span> <span class="p">(</span><span class="n">expansion</span> <span class="p">(</span><span class="n">shape</span> <span class="n">xs</span><span class="p">)</span> <span class="n">ys</span><span class="p">)</span>  <span class="o">&lt;=&gt;</span>  <span class="n">isJust</span> <span class="p">(</span><span class="n">restriction</span> <span class="p">(</span><span class="n">shape</span> <span class="n">ys</span><span class="p">)</span> <span class="n">xs</span><span class="p">)</span>
</code></pre></div></div>

<p>Here’s a diagram demonstrating these, given <code class="language-plaintext highlighter-rouge">s = Set.fromList ['a']</code> and <code class="language-plaintext highlighter-rouge">u = Set.fromList ['a', 'b']</code>:</p>
<figure>
    <img src="/static/img/semialign-intuition/exchange_map_diagram.svg" alt="A diagram demonstrating the attribution of locations when expanding or restricting a map" />
</figure>

<details><summary>Here's a better attempt:</summary>

<figure class="highlight"><pre><code class="language-hs" data-lang="hs"><span class="kr">class</span> <span class="p">(</span><span class="kt">Functor</span> <span class="n">t</span><span class="p">,</span> <span class="kt">Foldable</span> <span class="n">t</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kt">PartiallyOrdered</span> <span class="n">t</span> <span class="kr">where</span>
  <span class="n">exchange</span> <span class="o">::</span> <span class="p">(</span><span class="n">t</span> <span class="n">a</span><span class="p">,</span> <span class="n">t</span> <span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="kt">Maybe</span> <span class="n">a</span><span class="p">),</span> <span class="n">t</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
  <span class="c1">-- If exchange (xs, ys) = Just (xs', ys') then:</span>
  <span class="c1">--   fst &lt;$&gt; xs' = ys  (corollary: shape xs' = shape ys)</span>
  <span class="c1">--   fst &lt;$&gt; ys' = xs  (corollary: shape ys' = shape xs)</span>
  <span class="c1">--   catMaybes (toList (snd &lt;$&gt; xs')) == toList xs</span>
  <span class="c1">--   toList (snd &lt;$&gt; ys) `isSubsequenceOf` toList ys</span>
  <span class="c1">-- If exchange (xs, ys) = Just (xs', _)</span>
  <span class="c1">-- and exchange (snd &lt;$&gt; xs', zs) = Just (xs'', _) then:</span>
  <span class="c1">--   exchange (xs, zs) = Just (xs'' &lt;&amp;&gt; \(x, mmy) -&gt; (x, join mmy), _)</span>
  <span class="c1">-- If exchange (ys, zs) = Just (_, zs')</span>
  <span class="c1">-- and exchange (xs, snd &lt;$&gt; zs') = Just (_, zs'') then:</span>
  <span class="c1">--   exchange (xs, zs) = Just (_, zs'')</span>
  <span class="c1">-- exchange (xs, xs) = Just (xs &lt;&amp;&gt; \x -&gt; (x, Just x), xs &lt;&amp;&gt; \x -&gt; (x, x))</span>

<span class="n">isSubshape</span> <span class="o">::</span> <span class="kt">PartiallyOrdered</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="kt">Bool</span>
<span class="n">isSubshape</span> <span class="n">s</span> <span class="n">u</span> <span class="o">=</span> <span class="n">isJust</span> <span class="o">$</span> <span class="n">exchange</span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">u</span><span class="p">)</span>
<span class="c1">-- corollary: 'isSubshape' is a partial order</span>

<span class="n">expansion</span> <span class="o">::</span> <span class="kt">PartiallyOrdered</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="n">a</span><span class="p">))</span>
<span class="n">expansion</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="n">exchange</span> <span class="p">(</span><span class="n">xs</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="p">(</span><span class="n">xs'</span><span class="p">,</span> <span class="kr">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">snd</span> <span class="o">&lt;$&gt;</span> <span class="n">xs'</span>
<span class="c1">-- corollary: expansion (shape xs) xs = Just (Just &lt;$&gt; xs)</span>
<span class="c1">-- corollary: fmap join &lt;$&gt; (expansion s xs &gt;&gt;= expansion u) = expansion u</span>

<span class="n">restriction</span> <span class="o">::</span> <span class="kt">PartiallyOrdered</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="p">(</span><span class="n">t</span> <span class="n">a</span><span class="p">)</span>
<span class="n">restriction</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="n">exchange</span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">xs</span><span class="p">)</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="p">(</span><span class="kr">_</span><span class="p">,</span> <span class="n">xs'</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">snd</span> <span class="o">&lt;$&gt;</span> <span class="n">xs'</span>
<span class="c1">-- corollary: restriction (shape xs) xs = Just xs</span>
<span class="c1">-- corollary: restriction s xs &gt;&gt;= restriction u = restriction u xs</span></code></pre></figure>

</details>

<p>This superclass should have a compatibility law with <code class="language-plaintext highlighter-rouge">Semialign</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expansion</span> <span class="p">(</span><span class="n">shape</span> <span class="p">(</span><span class="n">align</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">))</span> <span class="n">xs</span> <span class="o">=</span> <span class="kt">Just</span> <span class="p">(</span><span class="n">here</span> <span class="o">&lt;$&gt;</span> <span class="n">align</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">expansion</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="kt">Just</span> <span class="n">ys</span> <span class="kr">then</span><span class="o">:</span>
  <span class="n">align</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="n">maybe</span> <span class="p">(</span><span class="kt">This</span> <span class="nb">()</span><span class="p">)</span> <span class="p">(</span><span class="kt">These</span> <span class="nb">()</span><span class="p">)</span> <span class="o">&lt;$&gt;</span> <span class="n">ys</span>
</code></pre></div></div>

<details><summary>Nested aside: restriction</summary>
<code>Semialign</code> only captures the expansion part of the relation. To capture the restriction part too, we would need to change the type of <code>align</code> to something like:

<figure class="highlight"><pre><code class="language-hs" data-lang="hs"><span class="n">align</span> <span class="o">::</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">t</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a</span> <span class="n">b</span><span class="p">),</span> <span class="n">t</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="kt">Maybe</span> <span class="n">b</span><span class="p">),</span> <span class="n">t</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
<span class="c1">-- expand the two inputs to the union shape, but also restrict the union shape</span>
<span class="c1">-- to the two shapes of the inputs</span></code></pre></figure>

</details>

<h2 id="intersections">Intersections</h2>

<p>Where there’s a transitive relation, there’s potential for duality. The opposite of a least upper bound is a <em>greatest lower bound</em>. That means for <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code> there’s a “lower bound” L (<code class="language-plaintext highlighter-rouge">L &lt;= x</code> and <code class="language-plaintext highlighter-rouge">L &lt;= y</code>) that is “greatest” (if <code class="language-plaintext highlighter-rouge">M &lt;= x</code> and <code class="language-plaintext highlighter-rouge">M &lt;= y</code> then <code class="language-plaintext highlighter-rouge">M &lt;= L</code>).</p>

<p>Equivalently, it means there’s a binary operation which we’ll denote <code class="language-plaintext highlighter-rouge">meet</code> that is commutative, associative, and idempotent. How is it different from <code class="language-plaintext highlighter-rouge">join</code>? Well, it isn’t really. But it has a different connection to the <code class="language-plaintext highlighter-rouge">&lt;=</code> relation:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">&lt;=</span> <span class="n">y</span>  <span class="o">&lt;=&gt;</span>  <span class="n">x</span> <span class="o">=</span> <span class="n">meet</span> <span class="n">x</span> <span class="n">y</span>
</code></pre></div></div>
<p>So by defining <code class="language-plaintext highlighter-rouge">join</code> and <code class="language-plaintext highlighter-rouge">meet</code> on the same set, we mean that they share the same <code class="language-plaintext highlighter-rouge">&lt;=</code> relation, which actually means they are a kind of opposites:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="n">meet</span> <span class="n">x</span> <span class="n">y</span>  <span class="o">&lt;=&gt;</span>  <span class="n">y</span> <span class="o">=</span> <span class="n">join</span> <span class="n">x</span> <span class="n">y</span>
</code></pre></div></div>
<p>A more common way to write this is called the “absorption laws”:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">join</span> <span class="n">x</span> <span class="p">(</span><span class="n">meet</span> <span class="n">x</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span>
<span class="n">meet</span> <span class="n">x</span> <span class="p">(</span><span class="n">join</span> <span class="n">x</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span>
</code></pre></div></div>
<p>Once these are satified, the resulting combination of the join-semilattice and the meet-semilattice is called a <em>lattice</em>.</p>

<p>For example the opposite of <code class="language-plaintext highlighter-rouge">max</code> on naturals is <code class="language-plaintext highlighter-rouge">min</code>, and the opposite of unions of sets is intersection of sets. What does this mean for our containers? That we’re defining an operation that takes the shorter of the two lists, or the intersection of two maps. Such operations are also useful quite often, and it helps that they are special cases of a general pattern. The <code class="language-plaintext highlighter-rouge">semialign</code> library defines a typeclass with a method named <code class="language-plaintext highlighter-rouge">zip</code> for this purpose.</p>

<p>For elements, just like <code class="language-plaintext highlighter-rouge">align</code> expands the two inputs to the smallest shape that can fit them both, <code class="language-plaintext highlighter-rouge">zip</code> would have to <em>restrict</em> the two inputs to the largest shape that fits inside them both. Restriction never skips over locations in the output, so the type is:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">zip</span> <span class="o">::</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
</code></pre></div></div>
<p>Restriction may however drop input elements. But it does preserve order:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">toList</span> <span class="p">(</span><span class="n">zip</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span> <span class="p">`</span><span class="n">isSubsequenceOf</span><span class="p">`</span> <span class="n">toList</span> <span class="n">xs</span>
</code></pre></div></div>

<p>Restriction has compatibility laws with <code class="language-plaintext highlighter-rouge">zip</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">restriction</span> <span class="p">(</span><span class="n">shape</span> <span class="p">(</span><span class="n">zip</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">))</span> <span class="n">xs</span> <span class="o">=</span> <span class="kt">Just</span> <span class="p">(</span><span class="n">fst</span> <span class="o">&lt;$&gt;</span> <span class="n">zip</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">restriction</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="kt">Just</span> <span class="n">ys</span> <span class="kr">then</span><span class="o">:</span>
  <span class="n">zip</span> <span class="n">s</span> <span class="n">xs</span> <span class="o">=</span> <span class="p">(</span><span class="nb">()</span><span class="p">,)</span> <span class="o">&lt;$&gt;</span> <span class="n">ys</span>
</code></pre></div></div>

<details><summary>Nested aside 2: expansion</summary>
Dually, <code>Zip</code> doesn't capture the expansion part of the relation. We would need to modify <code>zip</code> to also return the input shapes with the intersection shape expanded into them.
</details>

<h2 id="distributivity">Distributivity</h2>

<p>An additional requirement that <code class="language-plaintext highlighter-rouge">join</code> and <code class="language-plaintext highlighter-rouge">meet</code> can satisfy is <em>distributivity</em>. This is expressed as either of the two equivalent conditions:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">join</span> <span class="p">(</span><span class="n">meet</span> <span class="n">s</span> <span class="n">t</span><span class="p">)</span> <span class="n">u</span> <span class="o">=</span> <span class="n">meet</span> <span class="p">(</span><span class="n">join</span> <span class="n">s</span> <span class="n">u</span><span class="p">)</span> <span class="p">(</span><span class="n">join</span> <span class="n">t</span> <span class="n">u</span><span class="p">)</span>
<span class="n">meet</span> <span class="p">(</span><span class="n">join</span> <span class="n">s</span> <span class="n">t</span><span class="p">)</span> <span class="n">u</span> <span class="o">=</span> <span class="n">join</span> <span class="p">(</span><span class="n">meet</span> <span class="n">s</span> <span class="n">u</span><span class="p">)</span> <span class="p">(</span><span class="n">meet</span> <span class="n">t</span> <span class="n">u</span><span class="p">)</span>
</code></pre></div></div>
<p>However, care must be taken with generalizing these properties from mere shapes to elements. Suppose <code class="language-plaintext highlighter-rouge">xs :: t a</code>, <code class="language-plaintext highlighter-rouge">ys :: t b</code>, <code class="language-plaintext highlighter-rouge">zs :: t c</code>, then:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">align</span> <span class="p">(</span><span class="n">zip</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span> <span class="n">zs</span> <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">These</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="n">c</span><span class="p">)</span>
<span class="n">zip</span> <span class="p">(</span><span class="n">align</span> <span class="n">xs</span> <span class="n">zs</span><span class="p">)</span> <span class="p">(</span><span class="n">align</span> <span class="n">ys</span> <span class="n">zs</span><span class="p">)</span> <span class="o">::</span> <span class="n">t</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a</span> <span class="n">c</span><span class="p">,</span> <span class="kt">These</span> <span class="n">b</span> <span class="n">c</span><span class="p">)</span>
</code></pre></div></div>
<p>Suppose there’s a shape <code class="language-plaintext highlighter-rouge">s</code> such that <code class="language-plaintext highlighter-rouge">s &lt;= shape xs</code> and <code class="language-plaintext highlighter-rouge">s &lt;= shape zs</code>, but <code class="language-plaintext highlighter-rouge">not (s &lt;= shape ys)</code>. Then it follows that <code class="language-plaintext highlighter-rouge">not (s &lt;= shape (zip xs ys))</code> and <code class="language-plaintext highlighter-rouge">s &lt;= shape (align (zip xs ys) zs)</code>. This means that elements of <code class="language-plaintext highlighter-rouge">xs</code> that can be restricted to <code class="language-plaintext highlighter-rouge">s</code> will have been dropped when zipping with <code class="language-plaintext highlighter-rouge">ys</code>, and elements of <code class="language-plaintext highlighter-rouge">align (zip xs ys) zs</code> that can be restricted to <code class="language-plaintext highlighter-rouge">s</code> will never be attributed to anything from <code class="language-plaintext highlighter-rouge">xs</code>. On the other hand, we have <code class="language-plaintext highlighter-rouge">s &lt;= shape (align xs zs)</code>, meaning <code class="language-plaintext highlighter-rouge">zip (align xs zs)</code> retains elements of <code class="language-plaintext highlighter-rouge">xs</code> that can be restricted to <code class="language-plaintext highlighter-rouge">s</code>, and thus so does <code class="language-plaintext highlighter-rouge">zip (align xs zs) (align ys zs)</code>.</p>

<p>In general, we can consult the truth table of <code class="language-plaintext highlighter-rouge">(X and Y) or Z</code>, which is equivalent to <code class="language-plaintext highlighter-rouge">(X or Z) and (Y or Z)</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>X Y Z | (X and Y) or Z
------+---------------
0 0 0 | 0
0 0 1 | 1
0 1 0 | 0
0 1 1 | 1
1 0 0 | 0
1 0 1 | 1
1 1 0 | 1
1 1 1 | 1
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">1</code> entries correspond to presence of elements, and <code class="language-plaintext highlighter-rouge">0</code>s correspond to absence. Thus the most general type of element that we can use for the result of combining 3 containers this way is a datatype with 5 constructors, which can be mapped to <code class="language-plaintext highlighter-rouge">These (a, b) c</code> and <code class="language-plaintext highlighter-rouge">(These a c, These b c)</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">AndOr</span> <span class="n">a</span> <span class="n">b</span> <span class="n">c</span>
  <span class="o">=</span> <span class="kt">C</span> <span class="n">c</span>
  <span class="o">|</span> <span class="kt">BC</span> <span class="n">b</span> <span class="n">c</span>
  <span class="o">|</span> <span class="kt">AC</span> <span class="n">a</span> <span class="n">c</span>
  <span class="o">|</span> <span class="kt">AB</span> <span class="n">a</span> <span class="n">b</span>
  <span class="o">|</span> <span class="kt">ABC</span> <span class="n">a</span> <span class="n">b</span> <span class="n">c</span>

<span class="n">toThesePair</span> <span class="o">::</span> <span class="kt">AndOr</span> <span class="n">a</span> <span class="n">b</span> <span class="n">c</span> <span class="o">-&gt;</span> <span class="kt">These</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="n">c</span>
<span class="n">toThesePair</span> <span class="p">(</span><span class="kt">C</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="kt">That</span> <span class="n">z</span>
<span class="n">toThesePair</span> <span class="p">(</span><span class="kt">BC</span> <span class="n">_y</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="kt">That</span> <span class="n">z</span>
<span class="n">toThesePair</span> <span class="p">(</span><span class="kt">AC</span> <span class="n">_x</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="kt">That</span> <span class="n">z</span>
<span class="n">toThesePair</span> <span class="p">(</span><span class="kt">AB</span> <span class="n">x</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="kt">This</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
<span class="n">toThesePair</span> <span class="p">(</span><span class="kt">ABC</span> <span class="n">x</span> <span class="n">y</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="kt">These</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="n">z</span>

<span class="n">toPairThese</span> <span class="o">::</span> <span class="kt">AndOr</span> <span class="n">a</span> <span class="n">b</span> <span class="n">c</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a</span> <span class="n">c</span><span class="p">,</span> <span class="kt">These</span> <span class="n">b</span> <span class="n">c</span><span class="p">)</span>
<span class="n">toPairThese</span> <span class="p">(</span><span class="kt">C</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="kt">That</span> <span class="n">z</span><span class="p">,</span> <span class="kt">That</span> <span class="n">z</span><span class="p">)</span>
<span class="n">toPairThese</span> <span class="p">(</span><span class="kt">BC</span> <span class="n">y</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="kt">That</span> <span class="n">z</span><span class="p">,</span> <span class="kt">These</span> <span class="n">y</span> <span class="n">z</span><span class="p">)</span>
<span class="n">toPairThese</span> <span class="p">(</span><span class="kt">AC</span> <span class="n">x</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="kt">These</span> <span class="n">x</span> <span class="n">z</span><span class="p">,</span> <span class="kt">That</span> <span class="n">z</span><span class="p">)</span>
<span class="n">toPairThese</span> <span class="p">(</span><span class="kt">AB</span> <span class="n">x</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="kt">This</span> <span class="n">x</span><span class="p">,</span> <span class="kt">This</span> <span class="n">y</span><span class="p">)</span>
<span class="n">toPairThese</span> <span class="p">(</span><span class="kt">ABC</span> <span class="n">x</span> <span class="n">y</span> <span class="n">z</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="kt">These</span> <span class="n">x</span> <span class="n">z</span><span class="p">,</span> <span class="kt">These</span> <span class="n">y</span> <span class="n">z</span><span class="p">)</span>
</code></pre></div></div>

<p>Note the dropping and duplication of data, which doesn’t let us express these as a simple function from <code class="language-plaintext highlighter-rouge">These (a, b) c</code> to <code class="language-plaintext highlighter-rouge">(These a c, These b c)</code> or the other way. Instead we have to assert that there exists some <code class="language-plaintext highlighter-rouge">rs :: t (AndOr a b c)</code> such that:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">align</span> <span class="p">(</span><span class="n">zip</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span> <span class="n">zs</span> <span class="o">=</span> <span class="n">toThesePair</span> <span class="o">&lt;$&gt;</span> <span class="n">rs</span>
<span class="n">zip</span> <span class="p">(</span><span class="n">align</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span> <span class="p">(</span><span class="n">align</span> <span class="n">ys</span> <span class="n">zs</span><span class="p">)</span> <span class="o">=</span> <span class="n">toPairThese</span> <span class="o">&lt;$&gt;</span> <span class="n">rs</span>
</code></pre></div></div>

<p>The other distributive law, relating <code class="language-plaintext highlighter-rouge">zip (align xs ys) zs</code> and <code class="language-plaintext highlighter-rouge">align (zip xs zs) (zip ys zs)</code> turns out to be simpler. The truth table for <code class="language-plaintext highlighter-rouge">(X or Y) and Z</code> shows that the most general type of element is equivalent to <code class="language-plaintext highlighter-rouge">(These a b, c)</code>, which can be turned into <code class="language-plaintext highlighter-rouge">These (a, c) (b, c)</code> using <code class="language-plaintext highlighter-rouge">distrPairThese</code>, thus the law is:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">distrPairThese</span> <span class="o">&lt;$&gt;</span> <span class="n">zip</span> <span class="p">(</span><span class="n">align</span> <span class="n">xs</span> <span class="n">ys</span><span class="p">)</span> <span class="n">zs</span> <span class="o">=</span> <span class="n">align</span> <span class="p">(</span><span class="n">zip</span> <span class="n">xs</span> <span class="n">zs</span><span class="p">)</span> <span class="p">(</span><span class="n">zip</span> <span class="n">ys</span> <span class="n">zs</span><span class="p">)</span>
</code></pre></div></div>

<p>Distributivity actually adds a lot of rigidity into the structure of a lattice, that we can use to more easily reason about them. <a href="https://en.wikipedia.org/wiki/Birkhoff%27s_representation_theorem">Birkhoff’s representation theorem</a> states that every finite distributive lattice is a sublattice of the lattice of sets, under regular operations of union and intersection. This means that <code class="language-plaintext highlighter-rouge">t ~ Map k</code> is in a sense a universal example, and every other example is merely restricting <code class="language-plaintext highlighter-rouge">Map k</code> to a subset of shapes. For example, lists can be thought of as <code class="language-plaintext highlighter-rouge">Map Natural</code> with the restriction that keys have to start from <code class="language-plaintext highlighter-rouge">0</code> and be consecutive.</p>

<p>Equivalently, every distributive lattice is a sublattice of the product of some copies of the two-element lattice, which corresponds to <code class="language-plaintext highlighter-rouge">t ~ Maybe</code>. This means that a container type can be “factored” into individual locations, each of which can be filled with an element or not, and the “names” of these locations can be consistent between the different shapes of the container. For example, “3rd element of the list” is a location that makes sense for all lists, but in some lists it’s merely not filled with an element.</p>

<details><summary>Caveat: the factors need not have exactly 1 element</summary>
Birkhoff's representation theorem technically talks about join-irreducible elements of the lattice, which for containers means a shape that is not the union of non-<code>nil</code> shapes. This leads to the following couple of pathological examples:


<figure class="highlight"><pre><code class="language-hs" data-lang="hs"><span class="kr">instance</span> <span class="kt">Semialign</span> <span class="p">(</span><span class="kt">Const</span> <span class="kt">Bool</span><span class="p">)</span> <span class="kr">where</span> <span class="n">align</span> <span class="p">(</span><span class="kt">Const</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="kt">Const</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Const</span> <span class="p">(</span><span class="n">x</span> <span class="o">||</span> <span class="n">y</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">Zip</span> <span class="p">(</span><span class="kt">Const</span> <span class="kt">Bool</span><span class="p">)</span> <span class="kr">where</span> <span class="n">zip</span> <span class="p">(</span><span class="kt">Const</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="kt">Const</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Const</span> <span class="p">(</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">y</span><span class="p">)</span>

<span class="kr">data</span> <span class="kt">Maybe2</span> <span class="n">a</span> <span class="o">=</span> <span class="kt">Just2</span> <span class="n">a</span> <span class="n">a</span> <span class="o">|</span> <span class="kt">Nothing2</span>
<span class="kr">instance</span> <span class="kt">Semialign</span> <span class="kt">Maybe2</span> <span class="c1">-- self-evident</span>
<span class="kr">instance</span> <span class="kt">Zip</span> <span class="kt">Maybe2</span> <span class="c1">-- self-evident</span></code></pre></figure>


The lattice of shapes in both cases is isomorphic to the two-element lattice, but in the first case the join-irreducible lattice element is <code>Const True</code>, which has no elements; and in the second case the join-irreducible lattice element is <code>Just2</code>, which has 2 elements.
</details>

<h2 id="full">Full</h2>

<p>The <code class="language-plaintext highlighter-rouge">nil</code> shape is the unit of <code class="language-plaintext highlighter-rouge">join</code>, and equivalently, the <em>least element</em> in the (semi)lattice. Similarly, <code class="language-plaintext highlighter-rouge">meet</code> can have a unit, or equivalently, the <em>greatest element</em>. A lattice with a least and a greatest element is called a <em>bounded lattice</em>.</p>

<p>We can call this <code class="language-plaintext highlighter-rouge">full :: Shape t</code>. Then we expect that the result of zipping with <code class="language-plaintext highlighter-rouge">full</code> will keep every location, i.e. <code class="language-plaintext highlighter-rouge">full</code> itself has an element for every possible location in any other shape it could be zipped with.</p>

<p>The <code class="language-plaintext highlighter-rouge">semialign</code> library again opts for a Yoneda encoding:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Zip</span> <span class="n">f</span> <span class="o">=&gt;</span> <span class="kt">Repeat</span> <span class="n">f</span> <span class="kr">where</span>
  <span class="n">repeat</span> <span class="o">::</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="n">a</span>
</code></pre></div></div>
<p>The two are related by <code class="language-plaintext highlighter-rouge">full = repeat ()</code> and <code class="language-plaintext highlighter-rouge">repeat x = x &lt;$ full</code>.</p>

<p>For lists this corresponds to an infinitely long list (hence the name of the method). For maps this would be a map with all keys present, though this is only possible with a <code class="language-plaintext highlighter-rouge">containers</code> map if the key type is finite.</p>

<h2 id="crosswalk">Crosswalk</h2>

<p>A common intuition for <code class="language-plaintext highlighter-rouge">Applicative</code> is that it allows us to define a family of ways to lift functions of arbitrary arity:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">liftAN</span>
  <span class="o">::</span> <span class="kt">Applicative</span> <span class="n">f</span>
  <span class="o">=&gt;</span> <span class="p">(</span><span class="n">a1</span> <span class="o">-&gt;</span> <span class="n">a2</span> <span class="o">-&gt;</span> <span class="o">...</span> <span class="o">-&gt;</span> <span class="n">an</span> <span class="o">-&gt;</span> <span class="n">r</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="n">a1</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="n">a2</span> <span class="o">-&gt;</span> <span class="o">...</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="n">an</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="n">r</span>
<span class="n">liftAN</span> <span class="n">f</span> <span class="n">xs1</span> <span class="n">xs2</span> <span class="o">...</span> <span class="n">xsn</span> <span class="o">=</span> <span class="n">f</span> <span class="o">&lt;$&gt;</span> <span class="n">xs1</span> <span class="o">&lt;*&gt;</span> <span class="n">xs2</span> <span class="o">&lt;*&gt;</span> <span class="o">...</span> <span class="o">&lt;*&gt;</span> <span class="n">xsn</span>
</code></pre></div></div>
<p>Equivalently, we can commute <code class="language-plaintext highlighter-rouge">f</code> with a tuple of arbitrary width:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pairN</span> <span class="o">::</span> <span class="kt">Applicative</span> <span class="n">f</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="n">f</span> <span class="n">a1</span><span class="p">,</span> <span class="n">f</span> <span class="n">a2</span><span class="p">,</span> <span class="o">...</span> <span class="p">,</span> <span class="n">f</span> <span class="n">an</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="p">(</span><span class="n">a1</span><span class="p">,</span> <span class="n">a2</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="n">an</span><span class="p">)</span>
<span class="n">pairN</span> <span class="p">(</span><span class="n">xs1</span><span class="p">,</span> <span class="n">xs2</span><span class="p">,</span> <span class="o">...</span> <span class="p">,</span> <span class="n">xsn</span><span class="p">)</span> <span class="o">=</span> <span class="n">liftAN</span> <span class="p">(,,</span> <span class="o">...</span> <span class="p">,,)</span> <span class="n">xs1</span> <span class="n">xs2</span> <span class="o">...</span> <span class="n">xsn</span>
</code></pre></div></div>

<p>Let’s consider <code class="language-plaintext highlighter-rouge">Semialign</code> on the other hand. If we apply <code class="language-plaintext highlighter-rouge">align</code> twice to combine 3 containers, we end up with a type of elements like <code class="language-plaintext highlighter-rouge">These a (These b c)</code>. If we enumerate the possibilities, we can see that this type encodes a non-empty sub-tuple of <code class="language-plaintext highlighter-rouge">(a, b, c)</code>. In general, applying <code class="language-plaintext highlighter-rouge">align</code> multiple times to combine N containers will yield a container with some nesting of <code class="language-plaintext highlighter-rouge">These</code> that encodes a non-empty sub-tuple of N types:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">theseN</span>
  <span class="o">::</span> <span class="kt">Align</span> <span class="n">f</span>
  <span class="o">=&gt;</span> <span class="p">(</span><span class="n">f</span> <span class="n">a1</span><span class="p">,</span> <span class="n">f</span> <span class="n">a2</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="n">f</span> <span class="n">an</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a1</span> <span class="p">(</span><span class="kt">These</span> <span class="n">a2</span> <span class="p">(</span><span class="o">...</span> <span class="p">(</span><span class="kt">These</span> <span class="n">an</span> <span class="kt">Void</span><span class="p">)</span> <span class="o">...</span><span class="p">)))</span>
<span class="n">theseN</span> <span class="p">(</span><span class="n">xs1</span><span class="p">,</span> <span class="n">xs2</span><span class="p">,</span> <span class="o">...</span> <span class="p">,</span> <span class="n">xsn</span><span class="p">)</span> <span class="o">=</span> <span class="n">align</span> <span class="n">xs1</span> <span class="p">(</span><span class="n">align</span> <span class="n">xs2</span> <span class="p">(</span><span class="o">...</span> <span class="p">(</span><span class="n">align</span> <span class="n">xsn</span> <span class="n">nil</span><span class="p">)</span> <span class="o">...</span><span class="p">))</span>
</code></pre></div></div>

<p>We can think of <code class="language-plaintext highlighter-rouge">sequenceA</code> as taking a container apart into its shape and list of N elements, then applying the applicative <code class="language-plaintext highlighter-rouge">pairN</code> to the elements, then fmapping the result with a function that combines the N elements and the shape back into the container. Analogously, we can envision a function that takes a container apart into its shape and list of N elements, then applies <code class="language-plaintext highlighter-rouge">theseN</code> to the elements, then fmaps the result with a function that reassembles the container. This function will receive only a non-empty subset of the elements, and so may need to change the shape of the container.</p>

<p>The <code class="language-plaintext highlighter-rouge">semialign</code> library calls this <code class="language-plaintext highlighter-rouge">Crosswalk</code>, and provides an interface similar to <code class="language-plaintext highlighter-rouge">Traversable</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="p">(</span><span class="kt">Functor</span> <span class="n">t</span><span class="p">,</span> <span class="kt">Foldable</span> <span class="n">t</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kt">Crosswalk</span> <span class="n">t</span> <span class="kr">where</span>
  <span class="n">sequenceL</span> <span class="o">::</span> <span class="kt">Align</span> <span class="n">f</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="p">(</span><span class="n">f</span> <span class="n">a</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="p">(</span><span class="n">t</span> <span class="n">a</span><span class="p">)</span>
  <span class="n">crosswalk</span> <span class="o">::</span> <span class="kt">Align</span> <span class="n">f</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">t</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="n">f</span> <span class="p">(</span><span class="n">t</span> <span class="n">b</span><span class="p">)</span>
</code></pre></div></div>
<p>The intuition behind this class should be that a crosswalk allows you to select a non-empty subset of the locations in a container. Remembering that <code class="language-plaintext highlighter-rouge">Maybe</code> is an <code class="language-plaintext highlighter-rouge">Align</code>, we can see this in action:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sequenceL</span> <span class="o">@</span><span class="kr">_</span> <span class="o">@</span><span class="kt">Maybe</span> <span class="o">::</span> <span class="kt">Crosswalk</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="n">a</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Maybe</span> <span class="p">(</span><span class="n">t</span> <span class="n">a</span><span class="p">)</span>
</code></pre></div></div>
<p>This is similar to <code class="language-plaintext highlighter-rouge">catMaybes</code> (or its generalization in <a href="https://hackage.haskell.org/package/witherable-0.4.2/docs/Data-Witherable.html#v:catMaybes">witherable</a>), but with a twist: if the container is all <code class="language-plaintext highlighter-rouge">Nothing</code>s, we fail and return <code class="language-plaintext highlighter-rouge">Nothing</code>. As such, the container being crosswalked doesn’t have to support being empty.</p>

<p>The prototypical examples of a <code class="language-plaintext highlighter-rouge">Crosswalk</code> are:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">t ~ NonEmpty</code>: we can select a non-empty subset of locations, and after compacting the list by removing the holes we are guaranteed to have a non-empty list.</li>
  <li><code class="language-plaintext highlighter-rouge">t ~ []</code>: if the list is empty we return <code class="language-plaintext highlighter-rouge">nil</code>, otherwise we behave just as in the case of <code class="language-plaintext highlighter-rouge">NonEmpty</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">t ~ Map k</code>: we select which locations (keys) to retain, and which to remove.</li>
</ul>

<details><summary><code>semialign</code> doesn't provide instances for <code>NonEmpty</code> and <code>Map k</code> so here they are</summary>

<figure class="highlight"><pre><code class="language-hs" data-lang="hs"><span class="kr">instance</span> <span class="kt">Crosswalk</span> <span class="kt">NonEmpty</span> <span class="kr">where</span>
  <span class="n">crosswalk</span> <span class="n">f</span> <span class="p">(</span><span class="n">x</span> <span class="kt">NonEmpty</span><span class="o">.:|</span> <span class="kt">[]</span><span class="p">)</span> <span class="o">=</span> <span class="kt">NonEmpty</span><span class="o">.</span><span class="n">singleton</span> <span class="o">&lt;$&gt;</span> <span class="n">f</span> <span class="n">x</span>
  <span class="n">crosswalk</span> <span class="n">f</span> <span class="p">(</span><span class="n">x</span> <span class="kt">NonEmpty</span><span class="o">.:|</span> <span class="n">y</span><span class="o">:</span><span class="n">zs</span><span class="p">)</span>
    <span class="o">=</span> <span class="n">alignWith</span> <span class="n">assemble</span> <span class="p">(</span><span class="n">f</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="n">crosswalk</span> <span class="n">f</span> <span class="p">(</span><span class="n">y</span> <span class="kt">NonEmpty</span><span class="o">.:|</span> <span class="n">zs</span><span class="p">))</span>
    <span class="kr">where</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">This</span> <span class="n">x</span><span class="p">)</span> <span class="o">=</span> <span class="kt">NonEmpty</span><span class="o">.</span><span class="n">singleton</span> <span class="n">x</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">That</span> <span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">xs</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">These</span> <span class="n">x</span> <span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span> <span class="kt">NonEmpty</span><span class="o">.&lt;|</span> <span class="n">xs</span>

<span class="kr">instance</span> <span class="kt">Crosswalk</span> <span class="p">(</span><span class="kt">Map</span> <span class="n">k</span><span class="p">)</span> <span class="kr">where</span>
  <span class="n">crosswalk</span> <span class="kr">_</span> <span class="kt">Map</span><span class="o">.</span><span class="kt">Tip</span> <span class="o">=</span> <span class="n">nil</span>
  <span class="n">crosswalk</span> <span class="n">f</span> <span class="p">(</span><span class="kt">Map</span><span class="o">.</span><span class="kt">Bin</span> <span class="kr">_</span> <span class="n">k</span> <span class="n">v</span> <span class="n">l</span> <span class="n">r</span><span class="p">)</span>
    <span class="o">=</span> <span class="n">assemble</span> <span class="o">&lt;$&gt;</span> <span class="p">(</span><span class="n">crosswalk</span> <span class="n">f</span> <span class="n">l</span> <span class="p">`</span><span class="n">align</span><span class="p">`</span> <span class="n">f</span> <span class="n">v</span> <span class="p">`</span><span class="n">align</span><span class="p">`</span> <span class="n">crosswalk</span> <span class="n">f</span> <span class="n">r</span><span class="p">)</span>
    <span class="kr">where</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">This</span> <span class="p">(</span><span class="kt">This</span> <span class="n">l'</span><span class="p">))</span> <span class="o">=</span> <span class="n">l'</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">This</span> <span class="p">(</span><span class="kt">That</span> <span class="n">v'</span><span class="p">))</span> <span class="o">=</span> <span class="kt">Map</span><span class="o">.</span><span class="n">singleton</span> <span class="n">k</span> <span class="n">v'</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">That</span> <span class="n">r'</span><span class="p">)</span> <span class="o">=</span> <span class="n">r'</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">These</span> <span class="p">(</span><span class="kt">That</span> <span class="n">v'</span><span class="p">)</span> <span class="n">r'</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Map</span><span class="o">.</span><span class="n">insertMin</span> <span class="n">k</span> <span class="n">v'</span> <span class="n">r'</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">This</span> <span class="p">(</span><span class="kt">These</span> <span class="n">l'</span> <span class="n">v'</span><span class="p">))</span> <span class="o">=</span> <span class="kt">Map</span><span class="o">.</span><span class="n">insertMax</span> <span class="n">k</span> <span class="n">v'</span> <span class="n">l'</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">These</span> <span class="p">(</span><span class="kt">This</span> <span class="n">l'</span><span class="p">)</span> <span class="n">r'</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Map</span><span class="o">.</span><span class="n">glue</span> <span class="n">l'</span> <span class="n">r'</span>
      <span class="n">assemble</span> <span class="p">(</span><span class="kt">These</span> <span class="p">(</span><span class="kt">These</span> <span class="n">l'</span> <span class="n">v'</span><span class="p">)</span> <span class="n">r'</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Map</span><span class="o">.</span><span class="n">link</span> <span class="n">k</span> <span class="n">v'</span> <span class="n">l'</span> <span class="n">r'</span></code></pre></figure>

</details>

<p>More generally, <code class="language-plaintext highlighter-rouge">sequenceL</code> can be thought of as a generalized <code class="language-plaintext highlighter-rouge">transpose</code>, but not the kind that you get by traversing with a <code class="language-plaintext highlighter-rouge">ZipList</code>, but rather <a href="https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-List.html#v:transpose">the one from Data.List</a>, which can deal with lists of uneven length by skipping over holes.</p>

<p>Here’s a diagram desmonstrating an example with <code class="language-plaintext highlighter-rouge">t ~ []</code> and <code class="language-plaintext highlighter-rouge">f ~ Map Char</code>:</p>
<figure>
    <img src="/static/img/semialign-intuition/crosswalk_map_diagram.svg" alt="A diagram demonstrating selecting a subset of locations using crosswalk" />
</figure>
<p>Note how for locations that never occur in the <code class="language-plaintext highlighter-rouge">f</code> (e.g. the <code class="language-plaintext highlighter-rouge">'d'</code> key), we don’t produce a location in the result, thus we never need to construct an empty <code class="language-plaintext highlighter-rouge">t</code>.</p>

<p>Note that in general the <code class="language-plaintext highlighter-rouge">Crosswalk t</code> structure has nothing to with the <code class="language-plaintext highlighter-rouge">Semialign t</code> or the <code class="language-plaintext highlighter-rouge">Zip t</code> structure. For <code class="language-plaintext highlighter-rouge">t ~ Map k</code> they are compatible, but for lists <code class="language-plaintext highlighter-rouge">sequenceL</code> will skip over holes, and elements that follow holes will have been moved to different indices.</p>]]></content><author><name>mniip</name></author><summary type="html"><![CDATA[Shapes Unions Empty Aside: Lattices Intersections Distributivity Full Crosswalk Shapes As explained in the official docs for Traversable, a traversable functor can be intuitively decomposed into a “shape” and a list of “elements”: type Shape t = t () shape :: Functor t =&gt; t a -&gt; Shape t shape xs = () &lt;$ xs decompose :: Traversable t =&gt; t a -&gt; (Shape t, [a]) decompose xs = (shape xs, toList xs) We can even recombine the shape and the elements together, but note that length (shape xs) = length xs = length (toList xs), and this becomes a requirement for being able to recombine: recombine :: Traversable t =&gt; (Shape t, [a]) -&gt; t a recombine (s, elems) = case mapAccumL (\(x:xs) () -&gt; (xs, x)) elems s of ([], res) -&gt; res In a dependently typed language we could precisely encode this invariant with a dependent sum/dependent pair and a length-indexed vector: type Decomposition t a = { shape :: Shape t | Vector (length shape) a } In fancy math terms, we have a fibration shape :: t a -&gt; Shape t, whose total space is t a, and its base space is Shape t. Given a specific shape s :: Shape t, we have the fiber over s: a set of values of type t a that have shape s. As the above decomposition shows, this fiber is isomorphic to Vector (length s) a, i.e. a product of exactly length s copies of a. Examples: If t ~ [], we have Shape [] isomorphic to Natural — the shape of a list is uniquely determined by its length. If t ~ Map k, then Shape (Map k) is isomorphic to Set k — the shape of a map is its set of keys. Note that in this case multiple shapes can have the same length. If t ~ Maybe, then there are only two shapes: a shape for Nothing, of length 0; and a shape for Just, of length 1. There are no shapes of length 2 or more. The function length encodes a relationship between shapes and natural numbers. In general it can be arbitrary: given some set of shapes S and some function l :: S -&gt; Natural, we have a container: type C a = { s :: S | Vector (l s) a } such that Shape C is isomorphic to S, and length = l (via the aforementioned isomorphism). For a general traversable functor you know nothing about how its space of shapes is structured, and the Traversable interface is centered around the notion of preserving the shape: you work on one “container” at a time, and whatever shape comes in, also comes out. To work with multiple containers at once, we need additional structure to talk about how their shapes interact. Unions A problem often occurring in the real world is that of zipping lists together. There’s a function called Prelude.zip that tries to address this problem, but for lists of uneven length it truncates at the shorter list, and silently ignores the tail of the longer list. For some tasks this is unsuitable, and instead you want to take the longest of the two lists, padding the shorter one in some manner. As far as I can understand, this is the original problem that these and later semialign set out to solve. There we see things like: data These a b = This a | That b | These a b class Semialign t where align :: t a -&gt; t b -&gt; t (These a b) instance Semialign [] instance Ord k =&gt; Semialign (Map k) It may be tempting to assume that we can simply take the “longer” container, and then combine the elements by padding the “shorter” container. But, say, in case of Map k that’s not even well defined. For Map.singleton 'x' 3 and Map.singleton 'y' 2, neither can be said to be “longer” than the other, and what we probably want to get out of combining them is actually Map.fromList [('x', This 3), ('y', That 2)]. If we focus on what happens to the shapes of the containers: join :: Semialign t =&gt; Shape t -&gt; Shape t -&gt; Shape t join s u = () &lt;$ align s u we see that we’ve actually made up a binary operation that is: commutative: join s u = join u s, associative: join s (join u v) = join (join s u) v, and idempotent: join s s = s. For [], whose shapes are identified with natural numbers, this is the operation of taking the maximum of two numbers. For Map k, it is the operation of taking the union of two sets. What’s left is figuring out what happens to the elements. We’ve combined a container of shape s with length s elements and a container of shape u with length u elements, to obtain a container of shape join s u with length (join s u) elements. So how are the input elements related to the output elements? Parametricity forbids us from actually changing the element values, so we are restricted to talking about locations of the elements. We expect that the shape join s u has locations that were there in s, locations that were there in u, and locations that were there in both; but not locations that weren’t in either. This is enforced by giving align the following type: align :: t a -&gt; t b -&gt; t (These a b) In addition to the commutativity, associativity, and idempotency properties that can be expressed using just shapes, we have to preserve the structure of the elements. Elements form an ordered list, and align preserves this structure. As we map locations from s to join s u, we don’t lose or duplicate any, and we preserve their order: toList xs = mapMaybe here (toList (align xs ys)) where here :: These a b -&gt; Maybe a here (This x) = Just x here (That _) = Nothing here (These x _) = Just x The attribution of locations done by align can be demonstrated on a diagram like this: Empty Where there’s a binary operation, there’s a potential for having units. We want nil :: Shape t such that join xs nil = xs. For elements we expect a stronger property: an element of align xs nil is never attributed to an element of nil, thus: align xs nil = This &lt;$&gt; xs A way to enforce this using types is to say that actually nil :: t Void, and then align xs nil :: t (These a Void), and These a Void is forced to be the This constructor. This implies that nil has no elements (length nil = 0). The semialign library uses an equivalent formulation: class Semialign t =&gt; Align t where nil :: t a The left unit laws (join nil ys = ys, align nil ys = That &lt;$&gt; ys) follow from commutativity of align. Aside: Lattices A semilattice is a structure that can be defined from two different perspectives: Order-theoretically, it is a partially ordered set with some relation &lt;=, and it has least upper bounds: for any x and y there exists an “upper bound” L (x &lt;= L and y &lt;= L) that is “least” (if x &lt;= M and y &lt;= M then L &lt;= M). Algebraically, it is a set with a binary operation join that is commutative, associative, and idempotent. To connect the two we see that join x y is the least upper bound of x and y, and the relation &lt;= is defined by: x &lt;= y &lt;=&gt; y = join x y For example, the max operation on the naturals is connected to the ordinary &lt;= relation on the naturals. The union of sets is connected to the relation of being a subset. So partially ordered sets are a “superclass” of semilattices, but to observe this we have to forgo of the join binary operation and work with &lt;= instead. On shapes, &lt;= is a relation and doesn’t carry any data. With elements, we see that in the case that s &lt;= u, we have to introduce the data that relates the locations in s and the locations in u. We expect that a location in u is related to 0 or 1 locations in s, and that the order is preserved. This can be captured as a pair of maps: An expansion map Vector (length s) a -&gt; Vector (length u) (Maybe a) that doesn’t drop or duplicate elements, but may skip over some locations in the output (filling them with Nothing). A restriction map Vector (length u) a -&gt; Vector (length s) a that doesn’t duplicate elements or skip over locations, but may drop elements. In terms of the container type t, we could assign them types like: expansion :: Shape t -&gt; t a -&gt; Maybe (t (Maybe a)) restriction :: Shape t -&gt; t a -&gt; Maybe (t a) but these don’t capture nearly enough invariants, which we have to state as additional axioms: if expansion s xs = Just ys then: shape ys = s if restriction s xs = Just ys then: shape ys = s isJust (expansion (shape xs) ys) &lt;=&gt; isJust (restriction (shape ys) xs) Here’s a diagram demonstrating these, given s = Set.fromList ['a'] and u = Set.fromList ['a', 'b']: Here's a better attempt: class (Functor t, Foldable t) =&gt; PartiallyOrdered t where exchange :: (t a, t b) -&gt; Maybe (t (b, Maybe a), t (a, b)) -- If exchange (xs, ys) = Just (xs', ys') then: -- fst &lt;$&gt; xs' = ys (corollary: shape xs' = shape ys) -- fst &lt;$&gt; ys' = xs (corollary: shape ys' = shape xs) -- catMaybes (toList (snd &lt;$&gt; xs')) == toList xs -- toList (snd &lt;$&gt; ys) `isSubsequenceOf` toList ys -- If exchange (xs, ys) = Just (xs', _) -- and exchange (snd &lt;$&gt; xs', zs) = Just (xs'', _) then: -- exchange (xs, zs) = Just (xs'' &lt;&amp;&gt; \(x, mmy) -&gt; (x, join mmy), _) -- If exchange (ys, zs) = Just (_, zs') -- and exchange (xs, snd &lt;$&gt; zs') = Just (_, zs'') then: -- exchange (xs, zs) = Just (_, zs'') -- exchange (xs, xs) = Just (xs &lt;&amp;&gt; \x -&gt; (x, Just x), xs &lt;&amp;&gt; \x -&gt; (x, x)) isSubshape :: PartiallyOrdered t =&gt; Shape t -&gt; Shape t -&gt; Bool isSubshape s u = isJust $ exchange (s, u) -- corollary: 'isSubshape' is a partial order expansion :: PartiallyOrdered t =&gt; Shape t -&gt; t a -&gt; Maybe (t (Maybe a)) expansion s xs = exchange (xs, s) &lt;&amp;&gt; \(xs', _) -&gt; snd &lt;$&gt; xs' -- corollary: expansion (shape xs) xs = Just (Just &lt;$&gt; xs) -- corollary: fmap join &lt;$&gt; (expansion s xs &gt;&gt;= expansion u) = expansion u restriction :: PartiallyOrdered t =&gt; Shape t -&gt; t a -&gt; Maybe (t a) restriction s xs = exchange (s, xs) &lt;&amp;&gt; \(_, xs') -&gt; snd &lt;$&gt; xs' -- corollary: restriction (shape xs) xs = Just xs -- corollary: restriction s xs &gt;&gt;= restriction u = restriction u xs This superclass should have a compatibility law with Semialign: expansion (shape (align xs ys)) xs = Just (here &lt;$&gt; align xs ys) if expansion s xs = Just ys then: align s xs = maybe (This ()) (These ()) &lt;$&gt; ys Nested aside: restriction Semialign only captures the expansion part of the relation. To capture the restriction part too, we would need to change the type of align to something like: align :: t a -&gt; t b -&gt; (t (These a b), t (a, Maybe b), t (Maybe a, b)) -- expand the two inputs to the union shape, but also restrict the union shape -- to the two shapes of the inputs Intersections Where there’s a transitive relation, there’s potential for duality. The opposite of a least upper bound is a greatest lower bound. That means for x and y there’s a “lower bound” L (L &lt;= x and L &lt;= y) that is “greatest” (if M &lt;= x and M &lt;= y then M &lt;= L). Equivalently, it means there’s a binary operation which we’ll denote meet that is commutative, associative, and idempotent. How is it different from join? Well, it isn’t really. But it has a different connection to the &lt;= relation: x &lt;= y &lt;=&gt; x = meet x y So by defining join and meet on the same set, we mean that they share the same &lt;= relation, which actually means they are a kind of opposites: x = meet x y &lt;=&gt; y = join x y A more common way to write this is called the “absorption laws”: join x (meet x y) = x meet x (join x y) = x Once these are satified, the resulting combination of the join-semilattice and the meet-semilattice is called a lattice. For example the opposite of max on naturals is min, and the opposite of unions of sets is intersection of sets. What does this mean for our containers? That we’re defining an operation that takes the shorter of the two lists, or the intersection of two maps. Such operations are also useful quite often, and it helps that they are special cases of a general pattern. The semialign library defines a typeclass with a method named zip for this purpose. For elements, just like align expands the two inputs to the smallest shape that can fit them both, zip would have to restrict the two inputs to the largest shape that fits inside them both. Restriction never skips over locations in the output, so the type is: zip :: t a -&gt; t b -&gt; t (a, b) Restriction may however drop input elements. But it does preserve order: toList (zip xs ys) `isSubsequenceOf` toList xs Restriction has compatibility laws with zip: restriction (shape (zip xs ys)) xs = Just (fst &lt;$&gt; zip xs ys) if restriction s xs = Just ys then: zip s xs = ((),) &lt;$&gt; ys Nested aside 2: expansion Dually, Zip doesn't capture the expansion part of the relation. We would need to modify zip to also return the input shapes with the intersection shape expanded into them. Distributivity An additional requirement that join and meet can satisfy is distributivity. This is expressed as either of the two equivalent conditions: join (meet s t) u = meet (join s u) (join t u) meet (join s t) u = join (meet s u) (meet t u) However, care must be taken with generalizing these properties from mere shapes to elements. Suppose xs :: t a, ys :: t b, zs :: t c, then: align (zip xs ys) zs :: t (These (a, b) c) zip (align xs zs) (align ys zs) :: t (These a c, These b c) Suppose there’s a shape s such that s &lt;= shape xs and s &lt;= shape zs, but not (s &lt;= shape ys). Then it follows that not (s &lt;= shape (zip xs ys)) and s &lt;= shape (align (zip xs ys) zs). This means that elements of xs that can be restricted to s will have been dropped when zipping with ys, and elements of align (zip xs ys) zs that can be restricted to s will never be attributed to anything from xs. On the other hand, we have s &lt;= shape (align xs zs), meaning zip (align xs zs) retains elements of xs that can be restricted to s, and thus so does zip (align xs zs) (align ys zs). In general, we can consult the truth table of (X and Y) or Z, which is equivalent to (X or Z) and (Y or Z): X Y Z | (X and Y) or Z ------+--------------- 0 0 0 | 0 0 0 1 | 1 0 1 0 | 0 0 1 1 | 1 1 0 0 | 0 1 0 1 | 1 1 1 0 | 1 1 1 1 | 1 The 1 entries correspond to presence of elements, and 0s correspond to absence. Thus the most general type of element that we can use for the result of combining 3 containers this way is a datatype with 5 constructors, which can be mapped to These (a, b) c and (These a c, These b c): data AndOr a b c = C c | BC b c | AC a c | AB a b | ABC a b c toThesePair :: AndOr a b c -&gt; These (a, b) c toThesePair (C z) = That z toThesePair (BC _y z) = That z toThesePair (AC _x z) = That z toThesePair (AB x y) = This (x, y) toThesePair (ABC x y z) = These (x, y) z toPairThese :: AndOr a b c -&gt; (These a c, These b c) toPairThese (C z) = (That z, That z) toPairThese (BC y z) = (That z, These y z) toPairThese (AC x z) = (These x z, That z) toPairThese (AB x y) = (This x, This y) toPairThese (ABC x y z) = (These x z, These y z) Note the dropping and duplication of data, which doesn’t let us express these as a simple function from These (a, b) c to (These a c, These b c) or the other way. Instead we have to assert that there exists some rs :: t (AndOr a b c) such that: align (zip xs ys) zs = toThesePair &lt;$&gt; rs zip (align xs ys) (align ys zs) = toPairThese &lt;$&gt; rs The other distributive law, relating zip (align xs ys) zs and align (zip xs zs) (zip ys zs) turns out to be simpler. The truth table for (X or Y) and Z shows that the most general type of element is equivalent to (These a b, c), which can be turned into These (a, c) (b, c) using distrPairThese, thus the law is: distrPairThese &lt;$&gt; zip (align xs ys) zs = align (zip xs zs) (zip ys zs) Distributivity actually adds a lot of rigidity into the structure of a lattice, that we can use to more easily reason about them. Birkhoff’s representation theorem states that every finite distributive lattice is a sublattice of the lattice of sets, under regular operations of union and intersection. This means that t ~ Map k is in a sense a universal example, and every other example is merely restricting Map k to a subset of shapes. For example, lists can be thought of as Map Natural with the restriction that keys have to start from 0 and be consecutive. Equivalently, every distributive lattice is a sublattice of the product of some copies of the two-element lattice, which corresponds to t ~ Maybe. This means that a container type can be “factored” into individual locations, each of which can be filled with an element or not, and the “names” of these locations can be consistent between the different shapes of the container. For example, “3rd element of the list” is a location that makes sense for all lists, but in some lists it’s merely not filled with an element. Caveat: the factors need not have exactly 1 element Birkhoff's representation theorem technically talks about join-irreducible elements of the lattice, which for containers means a shape that is not the union of non-nil shapes. This leads to the following couple of pathological examples: instance Semialign (Const Bool) where align (Const x) (Const y) = Const (x || y) instance Zip (Const Bool) where zip (Const x) (Const y) = Const (x &amp;&amp; y) data Maybe2 a = Just2 a a | Nothing2 instance Semialign Maybe2 -- self-evident instance Zip Maybe2 -- self-evident The lattice of shapes in both cases is isomorphic to the two-element lattice, but in the first case the join-irreducible lattice element is Const True, which has no elements; and in the second case the join-irreducible lattice element is Just2, which has 2 elements. Full The nil shape is the unit of join, and equivalently, the least element in the (semi)lattice. Similarly, meet can have a unit, or equivalently, the greatest element. A lattice with a least and a greatest element is called a bounded lattice. We can call this full :: Shape t. Then we expect that the result of zipping with full will keep every location, i.e. full itself has an element for every possible location in any other shape it could be zipped with. The semialign library again opts for a Yoneda encoding: class Zip f =&gt; Repeat f where repeat :: a -&gt; f a The two are related by full = repeat () and repeat x = x &lt;$ full. For lists this corresponds to an infinitely long list (hence the name of the method). For maps this would be a map with all keys present, though this is only possible with a containers map if the key type is finite. Crosswalk A common intuition for Applicative is that it allows us to define a family of ways to lift functions of arbitrary arity: liftAN :: Applicative f =&gt; (a1 -&gt; a2 -&gt; ... -&gt; an -&gt; r) -&gt; f a1 -&gt; f a2 -&gt; ... -&gt; f an -&gt; f r liftAN f xs1 xs2 ... xsn = f &lt;$&gt; xs1 &lt;*&gt; xs2 &lt;*&gt; ... &lt;*&gt; xsn Equivalently, we can commute f with a tuple of arbitrary width: pairN :: Applicative f =&gt; (f a1, f a2, ... , f an) -&gt; f (a1, a2, ..., an) pairN (xs1, xs2, ... , xsn) = liftAN (,, ... ,,) xs1 xs2 ... xsn Let’s consider Semialign on the other hand. If we apply align twice to combine 3 containers, we end up with a type of elements like These a (These b c). If we enumerate the possibilities, we can see that this type encodes a non-empty sub-tuple of (a, b, c). In general, applying align multiple times to combine N containers will yield a container with some nesting of These that encodes a non-empty sub-tuple of N types: theseN :: Align f =&gt; (f a1, f a2, ..., f an) -&gt; f (These a1 (These a2 (... (These an Void) ...))) theseN (xs1, xs2, ... , xsn) = align xs1 (align xs2 (... (align xsn nil) ...)) We can think of sequenceA as taking a container apart into its shape and list of N elements, then applying the applicative pairN to the elements, then fmapping the result with a function that combines the N elements and the shape back into the container. Analogously, we can envision a function that takes a container apart into its shape and list of N elements, then applies theseN to the elements, then fmaps the result with a function that reassembles the container. This function will receive only a non-empty subset of the elements, and so may need to change the shape of the container. The semialign library calls this Crosswalk, and provides an interface similar to Traversable: class (Functor t, Foldable t) =&gt; Crosswalk t where sequenceL :: Align f =&gt; t (f a) -&gt; f (t a) crosswalk :: Align f =&gt; (a -&gt; f b) -&gt; t a -&gt; f (t b) The intuition behind this class should be that a crosswalk allows you to select a non-empty subset of the locations in a container. Remembering that Maybe is an Align, we can see this in action: sequenceL @_ @Maybe :: Crosswalk t =&gt; t (Maybe a) -&gt; Maybe (t a) This is similar to catMaybes (or its generalization in witherable), but with a twist: if the container is all Nothings, we fail and return Nothing. As such, the container being crosswalked doesn’t have to support being empty. The prototypical examples of a Crosswalk are: t ~ NonEmpty: we can select a non-empty subset of locations, and after compacting the list by removing the holes we are guaranteed to have a non-empty list. t ~ []: if the list is empty we return nil, otherwise we behave just as in the case of NonEmpty. t ~ Map k: we select which locations (keys) to retain, and which to remove. semialign doesn't provide instances for NonEmpty and Map k so here they are instance Crosswalk NonEmpty where crosswalk f (x NonEmpty.:| []) = NonEmpty.singleton &lt;$&gt; f x crosswalk f (x NonEmpty.:| y:zs) = alignWith assemble (f x) (crosswalk f (y NonEmpty.:| zs)) where assemble (This x) = NonEmpty.singleton x assemble (That xs) = xs assemble (These x xs) = x NonEmpty.&lt;| xs instance Crosswalk (Map k) where crosswalk _ Map.Tip = nil crosswalk f (Map.Bin _ k v l r) = assemble &lt;$&gt; (crosswalk f l `align` f v `align` crosswalk f r) where assemble (This (This l')) = l' assemble (This (That v')) = Map.singleton k v' assemble (That r') = r' assemble (These (That v') r') = Map.insertMin k v' r' assemble (This (These l' v')) = Map.insertMax k v' l' assemble (These (This l') r') = Map.glue l' r' assemble (These (These l' v') r') = Map.link k v' l' r' More generally, sequenceL can be thought of as a generalized transpose, but not the kind that you get by traversing with a ZipList, but rather the one from Data.List, which can deal with lists of uneven length by skipping over holes. Here’s a diagram desmonstrating an example with t ~ [] and f ~ Map Char: Note how for locations that never occur in the f (e.g. the 'd' key), we don’t produce a location in the result, thus we never need to construct an empty t. Note that in general the Crosswalk t structure has nothing to with the Semialign t or the Zip t structure. For t ~ Map k they are compatible, but for lists sequenceL will skip over holes, and elements that follow holes will have been moved to different indices.]]></summary></entry><entry><title type="html">Organizing Bulk Operations with Traversals</title><link href="https://mniip.com/posts/2023-10-24-organizing-bulk-operations-with-traversals" rel="alternate" type="text/html" title="Organizing Bulk Operations with Traversals" /><published>2023-10-24T00:00:00+00:00</published><updated>2023-10-24T00:00:00+00:00</updated><id>https://mniip.com/posts/organizing-bulk-operations-with-traversals</id><content type="html" xml:base="https://mniip.com/posts/2023-10-24-organizing-bulk-operations-with-traversals"><![CDATA[<h2 id="the-problem">The Problem</h2>

<p>Suppose, in Haskell pseudocode, you have some process:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">process</span> <span class="o">::</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="kt">B</span>
</code></pre></div></div>
<p>where <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">B</code> are arbitrary types. And suppose the implementation of the process is such that it is more efficient to do in bulk: you would provide several <code class="language-plaintext highlighter-rouge">A</code>’s upfront and would receive several <code class="language-plaintext highlighter-rouge">B</code>’s.</p>

<p>The question is, what type should you give to <code class="language-plaintext highlighter-rouge">processBulk</code>, the <em>bulk</em> version of <code class="language-plaintext highlighter-rouge">process</code>?
<!-- more --></p>

<p>A lot of the time the need for such bulk operations arises because you’re interacting with another system, crossing some sort of expensive API boundary. You could be talking to an SQL database, or a REST API if it has been thoughtfully extended with bulk endpoints. This API has its own signature, if not exactly a “type” then at least a pattern of how data flows in and out. It may be tempting to provide a thin wrapper around this pattern, and it’s common to see types like:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[A] -&gt; IO [B]</code> — here we have a bespoke invariant that the resulting list has the same length as the input list, and that the items are in the same order.</li>
  <li><code class="language-plaintext highlighter-rouge">[(Nonce, A)] -&gt; IO [(Nonce, B)]</code> or, with a clearer expression of intent, <code class="language-plaintext highlighter-rouge">Map Nonce A -&gt; IO (Map Nonce B)</code> — here we have a bespoke invariant that all keys present in the input map must also be present in the output map. Or maybe you’re intending that missing keys correspond to B’s that don’t exist? Then what you really have is <code class="language-plaintext highlighter-rouge">process :: A -&gt; IO (Maybe B)</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">[A] -&gt; IO [(A, B)]</code> or <code class="language-plaintext highlighter-rouge">Set A -&gt; IO (Map A B)</code> if maybe <code class="language-plaintext highlighter-rouge">A</code> is small enough to act as its own nonce. This would be your average “select multiple things by their IDs” SQL query. And we have a similar bespoke invariant here.</li>
</ul>

<p>In all cases we are faced with an invariant that is not captured by the type, and if you care about the correspondence between the input items and the output items, you may have trouble convincing the compiler that there is any. Say you chose <code class="language-plaintext highlighter-rouge">processBulk :: [A] -&gt; IO [B]</code>, and then you wanted to simplify and define <code class="language-plaintext highlighter-rouge">process</code> in terms of <code class="language-plaintext highlighter-rouge">processBulk</code>? Well now you’re faced with a partial function:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">process</span> <span class="o">::</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="kt">B</span>
<span class="n">process</span> <span class="n">a</span> <span class="o">=</span> <span class="n">processBulk</span> <span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="kr">case</span>
  <span class="p">[</span><span class="n">b</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="n">b</span>
  <span class="kr">_</span> <span class="o">-&gt;</span> <span class="n">error</span> <span class="s">"???"</span>
</code></pre></div></div>

<p>Perhaps <code class="language-plaintext highlighter-rouge">processBulk</code> is an internal function and you can look at what its use sites are. Some use sites might need to convert to one of the three aforementioned types anyway, and these conversions are fragile and error-prone, so why not hide it under the hood of <code class="language-plaintext highlighter-rouge">processBulk</code>. And if you have several use sites that require different formats you’re just screwed I guess?</p>

<h2 id="an-aside">An Aside</h2>

<p>If you’re not convinced by the utility of “optimizing” something that already works (why not just call <code class="language-plaintext highlighter-rouge">process</code> multiple times?), there are examples where bulk queries make a semantic difference.</p>

<p>Consider a procedure that does glob matching of multiple related files at once. Instead of asking for a random file that matches <code class="language-plaintext highlighter-rouge">*.hi</code> and a random file that matches <code class="language-plaintext highlighter-rouge">*.o</code>, you want a pair of files that match <code class="language-plaintext highlighter-rouge">$NAME.hi</code> and <code class="language-plaintext highlighter-rouge">$NAME.o</code> respectively, with a common string substituted for <code class="language-plaintext highlighter-rouge">$NAME</code>.</p>

<p>The same issue of returning a collection of outputs somehow connected to the collection of inputs arises here, and it’s actually semantically different from iterating over inputs one by one.</p>

<h2 id="a-solution">A Solution</h2>

<p>You may take the bespoke invariant challenge literally, and decide to use some sort of length-indexed vector to lift the exact invariant to the type level:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processBulk</span> <span class="o">::</span> <span class="n">forall</span> <span class="p">(</span><span class="n">n</span> <span class="o">::</span> <span class="kt">Nat</span><span class="p">),</span> <span class="kt">Vector</span> <span class="n">n</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Vector</span> <span class="n">n</span> <span class="kt">B</span><span class="p">)</span>

<span class="n">process</span> <span class="o">::</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="kt">B</span>
<span class="n">process</span> <span class="n">a</span> <span class="o">=</span> <span class="n">processBulk</span> <span class="p">(</span><span class="kt">Cons</span> <span class="n">a</span> <span class="kt">Nil</span><span class="p">)</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="kr">case</span>
  <span class="kt">Cons</span> <span class="n">b</span> <span class="kt">Nil</span> <span class="o">-&gt;</span> <span class="n">b</span>
  <span class="c1">-- pattern match is complete!</span>
</code></pre></div></div>
<p>or maybe a <a href="https://hackage.haskell.org/package/justified-containers">justified container</a>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processBulk</span> <span class="o">::</span> <span class="kt">JM</span><span class="o">.</span><span class="kt">Map</span> <span class="n">ph</span> <span class="n">nonce</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">JM</span><span class="o">.</span><span class="kt">Map</span> <span class="n">ph</span> <span class="n">nonce</span> <span class="kt">B</span><span class="p">)</span>

<span class="n">process</span> <span class="o">::</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="kt">B</span>
<span class="n">process</span> <span class="n">a</span> <span class="o">=</span> <span class="kt">JM</span><span class="o">.</span><span class="n">withSingleton</span> <span class="nb">()</span> <span class="n">a</span> <span class="nf">\</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span> <span class="o">-&gt;</span>
  <span class="n">process</span> <span class="n">m</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="n">m'</span> <span class="o">-&gt;</span> <span class="n">m'</span> <span class="kt">JM</span><span class="o">.!</span> <span class="n">k</span>
  <span class="c1">-- m' :: JM.Map ph () B;  k :: JM.Key ph ();  (JM.!) is total</span>
</code></pre></div></div>

<p>These can be downgraded back to their respective ordinary list and map versions if needed, but these do not in any way help with converting between lists and maps, which is still error-prone and possibly partial.</p>

<h2 id="a-better-solution">A Better Solution</h2>

<p>The power of Haskell’s type system allows us to write down a type that (with minor tweaks) is a generalization of all of the above, and more:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processBulk</span> <span class="o">::</span> <span class="n">forall</span> <span class="n">t</span><span class="o">.</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">B</span><span class="p">)</span>
</code></pre></div></div>
<p>You can straightforwardly specialize it with:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">t ~ []</code></li>
  <li><code class="language-plaintext highlighter-rouge">t ~ Map Nonce</code></li>
  <li><code class="language-plaintext highlighter-rouge">t ~ Compose [] ((,) Nonce)</code></li>
  <li><code class="language-plaintext highlighter-rouge">t ~ Vector n</code></li>
  <li><code class="language-plaintext highlighter-rouge">t ~ Map ph Nonce</code></li>
  <li><code class="language-plaintext highlighter-rouge">t ~ Identity</code></li>
</ul>

<p>You can also recover the “self-keyed” flavors with:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processBulk</span> <span class="o">.</span> <span class="kt">Map</span><span class="o">.</span><span class="n">fromSet</span> <span class="n">id</span>
  <span class="o">::</span> <span class="kt">Set</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Map</span> <span class="kt">A</span> <span class="kt">B</span><span class="p">)</span>

<span class="n">fmap</span> <span class="n">getCompose</span> <span class="o">.</span> <span class="n">processBulk</span> <span class="o">.</span> <span class="kt">Compose</span> <span class="o">.</span> <span class="n">map</span> <span class="p">(</span><span class="n">join</span> <span class="p">(,))</span>
  <span class="o">::</span> <span class="p">[</span><span class="kt">A</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">[(</span><span class="kt">A</span><span class="p">,</span> <span class="kt">B</span><span class="p">)]</span>
</code></pre></div></div>

<p>In cases where a bulk operation really is strictly an optimization of single operations, you can document this claim very concisely as a property test: <code class="language-plaintext highlighter-rouge">processBulk == traverse process</code>.</p>

<p>The implementation of such a function is actually relatively straightforward for the two kinds of “underlying interfaces” we’ve mentioned so far:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processList</span> <span class="o">::</span> <span class="p">[</span><span class="kt">A</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">[</span><span class="kt">B</span><span class="p">]</span>

<span class="n">processBulk</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">B</span><span class="p">)</span>
<span class="n">processBulk</span> <span class="n">as</span> <span class="o">=</span> <span class="n">processList</span> <span class="p">(</span><span class="n">toList</span> <span class="n">as</span><span class="p">)</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="n">bs</span> <span class="o">-&gt;</span>
  <span class="kr">case</span> <span class="n">mapAccumL</span> <span class="n">go</span> <span class="n">bs</span> <span class="n">as</span> <span class="kr">of</span>
    <span class="p">(</span><span class="kt">[]</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">res</span>
    <span class="p">(</span><span class="kr">_</span><span class="p">,</span> <span class="kr">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">error</span> <span class="s">"processList returned too many items"</span>
  <span class="kr">where</span>
    <span class="n">go</span> <span class="o">::</span> <span class="p">[</span><span class="kt">B</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="p">([</span><span class="kt">B</span><span class="p">],</span> <span class="kt">B</span><span class="p">)</span>
    <span class="n">go</span> <span class="p">(</span><span class="n">b</span><span class="o">:</span><span class="n">bs</span><span class="p">)</span> <span class="kr">_</span> <span class="o">=</span> <span class="p">(</span><span class="n">bs</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
    <span class="n">go</span> <span class="kt">[]</span> <span class="kr">_</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"processList returned too few items"</span>
</code></pre></div></div>

<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processMap</span> <span class="o">::</span> <span class="kt">Map</span> <span class="kt">Nonce</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Map</span> <span class="kt">Nonce</span> <span class="kt">B</span><span class="p">)</span>
<span class="n">randomNonce</span> <span class="o">::</span> <span class="kt">IO</span> <span class="kt">Nonce</span>

<span class="n">processBulk</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">B</span><span class="p">)</span>
<span class="n">processBulk</span> <span class="n">as</span> <span class="o">=</span> <span class="kr">do</span>
  <span class="n">tagged</span> <span class="o">&lt;-</span> <span class="n">for</span> <span class="n">as</span> <span class="nf">\</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kr">do</span>
    <span class="n">nonce</span> <span class="o">&lt;-</span> <span class="n">randomNonce</span>
    <span class="n">pure</span> <span class="p">(</span><span class="n">nonce</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span>
  <span class="n">m</span> <span class="o">&lt;-</span> <span class="n">processMap</span> <span class="p">(</span><span class="kt">Map</span><span class="o">.</span><span class="n">fromList</span> <span class="o">$</span> <span class="n">toList</span> <span class="n">tagged</span><span class="p">)</span>
  <span class="n">pure</span> <span class="o">$</span> <span class="n">tagged</span> <span class="o">&lt;&amp;&gt;</span> <span class="nf">\</span><span class="p">(</span><span class="n">nonce</span><span class="p">,</span> <span class="kr">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">m</span> <span class="kt">Map</span><span class="o">.!</span> <span class="n">nonce</span>
</code></pre></div></div>

<details><summary>Precise exceptions versions:</summary>

<figure class="highlight"><pre><code class="language-hs" data-lang="hs"><span class="n">processBulk</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">B</span><span class="p">)</span>
<span class="n">processBulk</span> <span class="n">as</span> <span class="o">=</span> <span class="n">processList</span> <span class="p">(</span><span class="n">toList</span> <span class="n">as</span><span class="p">)</span> <span class="o">&gt;&gt;=</span> <span class="nf">\</span><span class="n">bs</span> <span class="o">-&gt;</span>
  <span class="n">runStateT</span> <span class="p">(</span><span class="n">traverse</span> <span class="p">(</span><span class="kt">StateT</span> <span class="o">.</span> <span class="n">go</span><span class="p">)</span> <span class="n">as</span><span class="p">)</span> <span class="n">bs</span> <span class="o">&gt;&gt;=</span> <span class="nf">\</span><span class="kr">case</span>
    <span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="kt">[]</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pure</span> <span class="n">res</span>
    <span class="p">(</span><span class="kr">_</span><span class="p">,</span> <span class="kr">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">fail</span> <span class="s">"processList returned too many items"</span>
  <span class="kr">where</span>
    <span class="n">go</span> <span class="o">::</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">B</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">B</span><span class="p">,</span> <span class="p">[</span><span class="kt">B</span><span class="p">])</span>
    <span class="n">go</span> <span class="kr">_</span> <span class="p">(</span><span class="n">b</span><span class="o">:</span><span class="n">bs</span><span class="p">)</span> <span class="o">=</span> <span class="n">pure</span> <span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">bs</span><span class="p">)</span>
    <span class="n">go</span> <span class="kr">_</span> <span class="kt">[]</span> <span class="o">=</span> <span class="n">fail</span> <span class="s">"processList returned too few items"</span></code></pre></figure>


<figure class="highlight"><pre><code class="language-hs" data-lang="hs"><span class="n">processBulk</span> <span class="o">::</span> <span class="kt">Traversable</span> <span class="n">t</span> <span class="o">=&gt;</span> <span class="n">t</span> <span class="kt">A</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="p">(</span><span class="n">t</span> <span class="kt">B</span><span class="p">)</span>
<span class="n">processBulk</span> <span class="n">as</span> <span class="o">=</span> <span class="kr">do</span>
  <span class="n">tagged</span> <span class="o">&lt;-</span> <span class="n">for</span> <span class="n">as</span> <span class="nf">\</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kr">do</span>
    <span class="n">nonce</span> <span class="o">&lt;-</span> <span class="n">randomNonce</span>
    <span class="n">pure</span> <span class="p">(</span><span class="n">nonce</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span>
  <span class="n">m</span> <span class="o">&lt;-</span> <span class="n">processMap</span> <span class="p">(</span><span class="kt">Map</span><span class="o">.</span><span class="n">fromList</span> <span class="o">$</span> <span class="n">toList</span> <span class="n">tagged</span><span class="p">)</span>
  <span class="n">for</span> <span class="n">tagged</span> <span class="nf">\</span><span class="p">(</span><span class="n">nonce</span><span class="p">,</span> <span class="kr">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kr">case</span> <span class="kt">Map</span><span class="o">.</span><span class="n">lookup</span> <span class="n">nonce</span> <span class="n">m</span> <span class="kr">of</span>
    <span class="kt">Just</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="n">pure</span> <span class="n">b</span>
    <span class="kt">Nothing</span> <span class="o">-&gt;</span> <span class="n">fail</span> <span class="s">"processMap didn't return a key we've given it"</span></code></pre></figure>

</details>

<p>You will notice something here: in order to satisfy the type of <code class="language-plaintext highlighter-rouge">processBulk</code> we are forced to eagerly check that the expected invariant holds. This is good because in the event that <code class="language-plaintext highlighter-rouge">processList</code>/<code class="language-plaintext highlighter-rouge">processMap</code> are some sort of external interface, and that interface behaves unexpectedly — you’d rather find out about it early, than pass it to a Rube Goldberg machine of unchecked conversions that will just unexpectedly die at a random spot.</p>

<p>Even if your underlying interface and your use sites agree on e.g. using lists, and they don’t break terribly if the invariant is violated so you think you can afford not to check — even then I argue it is at least a good code organization measure. You have a type that clearly indicates intent: give me any structure with <code class="language-plaintext highlighter-rouge">A</code>’s and I’ll fill it with <code class="language-plaintext highlighter-rouge">B</code>’s instead. You have a clear separation of responsibilities: it is <code class="language-plaintext highlighter-rouge">processBulk</code>’s job to validate the invariant. You have a flexible building block that can be easily repurposed in case the use sites change how they want to see the data.</p>

<p>I don’t believe I am the first to discover this idea, at least I’ve been informed that @phadej <a href="https://oleg.fi/gists/posts/2023-10-12-use-traversals-for-batch-operations.html">was writing</a> about essentially the same thing. Still, other than that I don’t think I’ve seen another Haskell programmer talk about this technique, and I’ve definitely seen a lot of “bad” versions of <code class="language-plaintext highlighter-rouge">processBulk</code>.</p>

<h2 id="appendix-lens">Appendix: Lens</h2>

<p>It is possible to further generalize by abstracting out the type of <code class="language-plaintext highlighter-rouge">traverse</code>:</p>
<div class="language-hs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">processBulkOf</span> <span class="o">::</span> <span class="kt">Traversal</span> <span class="n">s</span> <span class="n">t</span> <span class="kt">A</span> <span class="kt">B</span> <span class="o">-&gt;</span> <span class="n">s</span> <span class="o">-&gt;</span> <span class="kt">IO</span> <span class="n">t</span>

<span class="n">processBulk</span> <span class="o">=</span> <span class="n">processBulkOf</span> <span class="n">traverse</span>
</code></pre></div></div>

<p>In this case the hypothetical property test becomes <code class="language-plaintext highlighter-rouge">processBulkOf == (%%~ process)</code>. An implementation of <code class="language-plaintext highlighter-rouge">processBulkOf</code> is left as an exercise to the reader. Note that the <code class="language-plaintext highlighter-rouge">mapAccumL</code>/<code class="language-plaintext highlighter-rouge">StateT</code> business is essentially what <code class="language-plaintext highlighter-rouge">unsafePartsOf</code> is.</p>

<p>This gets you the last 1% of flexibility over the 99% that <code class="language-plaintext highlighter-rouge">Traversable</code> gives you, but I would double check whether it’s really needed.</p>]]></content><author><name>mniip</name></author><summary type="html"><![CDATA[The Problem Suppose, in Haskell pseudocode, you have some process: process :: A -&gt; IO B where A and B are arbitrary types. And suppose the implementation of the process is such that it is more efficient to do in bulk: you would provide several A’s upfront and would receive several B’s. The question is, what type should you give to processBulk, the bulk version of process?]]></summary></entry></feed>