<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jzhonx.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jzhonx.github.io/" rel="alternate" type="text/html" /><updated>2026-09-03T09:44:59+00:00</updated><id>https://jzhonx.github.io/blog/feed.xml</id><title type="html">JzNext</title><subtitle>Go, Haskell, Declarative languages</subtitle><author><name>Junxiang Zhou</name></author><entry><title type="html">Benchmarking Zippers in Haskell</title><link href="https://jzhonx.github.io/blog/haskell/2026/07/16/zippers-perf.html" rel="alternate" type="text/html" title="Benchmarking Zippers in Haskell" /><published>2026-07-16T00:00:00+00:00</published><updated>2026-07-16T00:00:00+00:00</updated><id>https://jzhonx.github.io/blog/haskell/2026/07/16/zippers-perf</id><content type="html" xml:base="https://jzhonx.github.io/blog/haskell/2026/07/16/zippers-perf.html"><![CDATA[<p>In the <a href="/blog/haskell/2026/03/17/zippers.html">previous post</a>, we explored zippers and their applications in functional programming. In this post, we benchmark their performance against a root-based approach.</p>

<h2 id="two-approaches">Two Approaches</h2>

<p>We define a simple tree data structure and the naive root-based approach for traversing and modifying the tree.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">Tree</span>
  <span class="o">=</span> <span class="kt">Atom</span> <span class="o">!</span><span class="kt">Int</span> <span class="o">!</span><span class="kt">String</span>
  <span class="o">|</span> <span class="kt">Object</span> <span class="o">!</span><span class="kt">Int</span> <span class="o">!</span><span class="p">(</span><span class="kt">Map</span> <span class="kt">String</span> <span class="kt">Tree</span><span class="p">)</span>
  <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">,</span> <span class="kt">Eq</span><span class="p">,</span> <span class="kt">Generic</span><span class="p">,</span> <span class="kt">NFData</span><span class="p">)</span>

<span class="n">access</span> <span class="o">::</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span>
<span class="n">access</span> <span class="kt">[]</span> <span class="n">f</span> <span class="n">t</span> <span class="o">=</span> <span class="n">f</span> <span class="n">t</span>
<span class="n">access</span> <span class="p">(</span><span class="n">k</span> <span class="o">:</span> <span class="n">ks</span><span class="p">)</span> <span class="n">f</span> <span class="p">(</span><span class="kt">Object</span> <span class="n">vers</span> <span class="n">ts</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Object</span> <span class="n">vers</span> <span class="o">$</span> <span class="kt">Map</span><span class="o">.</span><span class="n">alter</span> <span class="n">modifyChild</span> <span class="n">k</span> <span class="n">ts</span>
 <span class="kr">where</span>
  <span class="n">modifyChild</span> <span class="kt">Nothing</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"Invalid path to access"</span>
  <span class="n">modifyChild</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">child</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Just</span> <span class="o">$</span> <span class="n">access</span> <span class="n">ks</span> <span class="n">f</span> <span class="n">child</span>
<span class="n">access</span> <span class="kr">_</span> <span class="kr">_</span> <span class="kr">_</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"Invalid path to access"</span>
</code></pre></div></div>

<p>Then we implement the zipper data structure and its operations for traversing and modifying the tree.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">Zipper</span> <span class="o">=</span> <span class="kt">Zipper</span>
  <span class="p">{</span> <span class="n">focus</span> <span class="o">::</span> <span class="o">!</span><span class="kt">Tree</span>
  <span class="p">,</span> <span class="n">breadcrumbs</span> <span class="o">::</span> <span class="p">[</span><span class="kt">Crumb</span><span class="p">]</span>
  <span class="p">}</span>
  <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">,</span> <span class="kt">Eq</span><span class="p">,</span> <span class="kt">Generic</span><span class="p">,</span> <span class="kt">NFData</span><span class="p">)</span>

<span class="kr">type</span> <span class="kt">Move</span> <span class="o">=</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>

<span class="kr">data</span> <span class="kt">Crumb</span> <span class="o">=</span> <span class="kt">Crumb</span>
  <span class="p">{</span> <span class="n">holeKey</span> <span class="o">::</span> <span class="o">!</span><span class="kt">String</span>
  <span class="p">,</span> <span class="n">storedVers</span> <span class="o">::</span> <span class="o">!</span><span class="kt">Int</span>
  <span class="p">,</span> <span class="n">siblings</span> <span class="o">::</span> <span class="o">!</span><span class="p">(</span><span class="kt">Map</span> <span class="kt">String</span> <span class="kt">Tree</span><span class="p">)</span>
  <span class="p">}</span>
  <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">,</span> <span class="kt">Eq</span><span class="p">,</span> <span class="kt">Generic</span><span class="p">,</span> <span class="kt">NFData</span><span class="p">)</span>

<span class="n">goDown</span> <span class="o">::</span> <span class="kt">String</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">goDown</span> <span class="n">k</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="p">(</span><span class="kt">Object</span> <span class="n">vers</span> <span class="n">ts</span><span class="p">)</span> <span class="n">bs</span><span class="p">)</span>
  <span class="o">|</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">child</span><span class="p">,</span> <span class="n">siblings'</span><span class="p">)</span> <span class="o">&lt;-</span> <span class="kt">Map</span><span class="o">.</span><span class="n">updateLookupWithKey</span> <span class="p">(</span><span class="nf">\</span><span class="kr">_</span> <span class="kr">_</span> <span class="o">-&gt;</span> <span class="kt">Nothing</span><span class="p">)</span> <span class="n">k</span> <span class="n">ts</span> <span class="o">=</span>
      <span class="kt">Zipper</span> <span class="n">child</span> <span class="p">(</span><span class="kt">Crumb</span> <span class="n">k</span> <span class="n">vers</span> <span class="n">siblings'</span> <span class="o">:</span> <span class="n">bs</span><span class="p">)</span>
<span class="n">goDown</span> <span class="n">k</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="n">f</span> <span class="kr">_</span><span class="p">)</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Cannot go to child '"</span> <span class="o">++</span> <span class="n">k</span> <span class="o">++</span> <span class="s">"' of tree: "</span> <span class="o">++</span> <span class="n">show</span> <span class="n">f</span>

<span class="n">goUp</span> <span class="o">::</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">goUp</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="n">t</span> <span class="p">(</span><span class="kt">Crumb</span> <span class="n">key</span> <span class="n">vers</span> <span class="n">siblings'</span> <span class="o">:</span> <span class="n">bs</span><span class="p">))</span> <span class="o">=</span> <span class="kt">Zipper</span> <span class="p">(</span><span class="kt">Object</span> <span class="n">vers</span> <span class="p">(</span><span class="kt">Map</span><span class="o">.</span><span class="n">insert</span> <span class="n">key</span> <span class="n">t</span> <span class="n">siblings'</span><span class="p">))</span> <span class="n">bs</span>
<span class="n">goUp</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="kr">_</span> <span class="kt">[]</span><span class="p">)</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"Already at the top"</span>
</code></pre></div></div>

<h2 id="benchmark-design">Benchmark Design</h2>

<p>Each benchmark performs 100,000 operations. Three full trees are generated with the following shapes:</p>

<table>
  <thead>
    <tr>
      <th>Depth × width</th>
      <th style="text-align: right">nodes</th>
      <th>Children per Map</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5 × 16</td>
      <td style="text-align: right">1,118,481</td>
      <td>16</td>
    </tr>
    <tr>
      <td>10 × 4</td>
      <td style="text-align: right">1,398,101</td>
      <td>4</td>
    </tr>
    <tr>
      <td>20 × 2</td>
      <td style="text-align: right">2,097,151</td>
      <td>2</td>
    </tr>
  </tbody>
</table>

<p>Here, depth counts edges from the root. All three trees have exactly 1,048,576 leaves, but their shapes differ.</p>

<p>The workloads are:</p>

<ol>
  <li><strong>Random lookup.</strong> Choose a path by selecting its depth uniformly from 1 through the maximum depth, then selecting
each key uniformly. Add the node’s integer to a checksum.</li>
  <li><strong>Random edit.</strong> Generate paths in the same way, then increment the node’s integer. Both <code class="language-plaintext highlighter-rouge">Atom</code> and <code class="language-plaintext highlighter-rouge">Object</code> are
updated.</li>
  <li><strong>Local edit.</strong> Start by editing a node at a random path. Before each following edit, move one or two levels up or
down. At a leaf, the next move must be up; after editing the root, restart at another random path. For example: <code class="language-plaintext highlighter-rouge">a/b</code>
(random start) -&gt; <code class="language-plaintext highlighter-rouge">a/b/c/d</code> (down two) -&gt; <code class="language-plaintext highlighter-rouge">a/b</code> (up two from a leaf) -&gt; <code class="language-plaintext highlighter-rouge">root</code> (up two) -&gt; <code class="language-plaintext highlighter-rouge">x/y/z</code> (random restart).</li>
</ol>

<h3 id="measurement-details">Measurement Details</h3>

<p>These results are based on running the benchmarks on a MacBook Air M4 with 24 GB of RAM, using the following environment:</p>

<ul>
  <li>arm64, macOS 26.3.1</li>
  <li>GHC 9.10.3, Cabal 3.12.1.0</li>
  <li>Criterion 1.6.5.0</li>
  <li>containers 0.7, random 1.3.1</li>
  <li>fixed seed 20260716</li>
</ul>

<p>The test program is compiled with <code class="language-plaintext highlighter-rouge">ghc -O2</code>.</p>

<p>Trees, paths, and relative zipper moves are generated and fully evaluated outside the timed region. Update benchmarks
use <code class="language-plaintext highlighter-rouge">whnf</code>: the strict tree fields and <code class="language-plaintext highlighter-rouge">Data.Map.Strict</code> force each update, without adding an
unrelated traversal of the entire result. The zipper is returned to the root at the end of each batch, so both update
implementations produce the same <code class="language-plaintext highlighter-rouge">Tree</code>.</p>

<p>The commands are:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">cabal run zippers-time
</span></code></pre></div></div>

<h2 id="timing-results">Timing Results</h2>

<p>Each time is Criterion’s mean for the entire 100,000-operation batch. Values are shown as root-based time / zipper time
— faster implementation and speedup. The speedup is the slower time divided by the faster time; for example, root 3.86×
means the zipper took 3.86 times as long as the root-based implementation.</p>

<table>
  <thead>
    <tr>
      <th>Tree</th>
      <th>Random lookup</th>
      <th>Random edit</th>
      <th>Local edit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5 × 16</td>
      <td>21.97 / 84.89 ms — root 3.86×</td>
      <td>53.41 / 90.09 ms — root 1.69×</td>
      <td>32.17 / 31.52 ms — roughly tied</td>
    </tr>
    <tr>
      <td>10 × 4</td>
      <td>20.96 / 84.93 ms — root 4.05×</td>
      <td>56.86 / 87.92 ms — root 1.55×</td>
      <td>26.18 / 16.61 ms — zipper 1.58×</td>
    </tr>
    <tr>
      <td>20 × 2</td>
      <td>33.76 / 114.34 ms — root 3.39×</td>
      <td>88.20 / 118.11 ms — root 1.34×</td>
      <td>30.45 / 9.14 ms — zipper 3.33×</td>
    </tr>
  </tbody>
</table>

<h2 id="analysis-of-the-results">Analysis of the Results</h2>

<p>A root-based lookup performs one <code class="language-plaintext highlighter-rouge">Map.lookup</code> at each level and allocates very little. The zipper has to update the map
and allocate memory whenever it moves down, so it is slower for random lookups.</p>

<p>Random edits narrow the gap because the root-based implementation must also rebuild every map on the path. The zipper is
still slower because it travels farther than the root-based implementation: it usually needs to climb close to the root
before moving to the next target. The path between two random targets is usually longer than the path from the root to
either target.</p>

<p>For the local workload, the root-based implementation starts over for every edit even though consecutive paths share
most of their prefix. On the deepest tree, the root-based implementation still traverses a long path for each edit,
while the zipper moves only about 1.5 edges between nearby targets. The root-based implementation is also much faster on
local edits than on random edits. This likely reflects better CPU-cache locality because consecutive operations revisit
the same branches.</p>

<p>The increasing advantage is not caused by depth in isolation: these test shapes become narrower as they become deeper.
Smaller maps make per-level operations cheaper for both implementations.</p>

<h2 id="practical-takeaway">Practical Takeaway</h2>

<ul>
  <li>Use the root-based approach for isolated or scattered operations, especially reads; for such workloads, a zipper is
usually not worth the overhead.</li>
  <li>For a batch of nearby edits, keep the zipper open between edits and convert it back to a complete tree only after the
batch.</li>
</ul>

<p>The <a href="https://github.com/jzhonx/jzhonx.github.io/tree/main/code/zippers_perf">complete benchmark source</a> is available in
the repository.</p>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li>Vít Šefl, <a href="https://arxiv.org/abs/1908.10926">Performance Analysis of Zippers</a>, 2019.</li>
</ul>]]></content><author><name>Junxiang Zhou</name></author><category term="Haskell" /><summary type="html"><![CDATA[In the previous post, we explored zippers and their applications in functional programming. In this post, we benchmark their performance against a root-based approach.]]></summary></entry><entry><title type="html">Understanding Zippers</title><link href="https://jzhonx.github.io/blog/haskell/2026/03/17/zippers.html" rel="alternate" type="text/html" title="Understanding Zippers" /><published>2026-03-17T00:00:00+00:00</published><updated>2026-03-17T00:00:00+00:00</updated><id>https://jzhonx.github.io/blog/haskell/2026/03/17/zippers</id><content type="html" xml:base="https://jzhonx.github.io/blog/haskell/2026/03/17/zippers.html"><![CDATA[<p>When working with tree-structured data, we often need to navigate to a specific node and modify it. In imperative languages, this is usually straightforward thanks to mutable state and parent pointers. In functional languages, however, immutability makes this pattern less obvious.</p>

<p>In this post, we’ll explore how to navigate and modify tree structures efficiently in functional languages using a technique called <strong>zippers</strong>.</p>

<h2 id="simple-json-query-language">Simple JSON query language</h2>

<p>Suppose we are implementing a simple JSON query tool. The language allows us to access and modify values in a JSON object.</p>

<p>The query language has four simple operations:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">set path = value</code> — replace the value at a path.</li>
  <li><code class="language-plaintext highlighter-rouge">get path</code> — read the value at a path.</li>
  <li><code class="language-plaintext highlighter-rouge">query | query</code> — run two queries from left to right.</li>
  <li><code class="language-plaintext highlighter-rouge">at path { query }</code> — move the cursor to a path and run a query relative to that node.</li>
</ul>

<p>In an imperative setting, we might represent the JSON as a tree with parent pointers, making navigation (both downward and upward) trivial.</p>

<p>In a functional language like Haskell, we generally avoid parent pointers because maintaining them correctly under immutability is difficult. Instead, we need a different approach.</p>

<h3 id="example">Example</h3>

<p>Suppose we have the following JSON object:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"a"</span><span class="p">:{</span><span class="w">
    </span><span class="nl">"b"</span><span class="p">:{</span><span class="w">
        </span><span class="nl">"x"</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="w">
        </span><span class="nl">"y"</span><span class="p">:</span><span class="mi">2</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>We have the following two queries that do the same thing but with different syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set .a.b.x = 42
| set .a.b.y = 43

at .a.b {
  set .x = 42
  | set .y = 43
}
</code></pre></div></div>

<p>Inside the <code class="language-plaintext highlighter-rouge">at</code> block, paths are relative to <code class="language-plaintext highlighter-rouge">.a.b</code>. When the block finishes, the cursor returns to its previous
position.</p>

<p>We will use this example to compare two ways of implementing the update operations in Haskell, and how to use zippers to
navigate and modify the tree data structure efficiently.</p>

<h2 id="navigating-and-modifying-trees">Navigating and Modifying Trees</h2>

<h3 id="persistent-data-structure">Persistent data structure</h3>

<p>In Haskell, data structures are typically immutable. To modify a node in a tree, we need to create a new tree that
contains the modified node, while sharing the unchanged nodes with the original tree. This is known as a <strong>persistent
data structure</strong>.</p>

<p>We define a minimal tree type:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">Tree</span>
  <span class="o">=</span> <span class="kt">Atom</span> <span class="kt">Int</span>
  <span class="o">|</span> <span class="kt">Object</span> <span class="p">[(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">Tree</span><span class="p">)]</span>
  <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">)</span>
</code></pre></div></div>

<p>Example value:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">root</span> <span class="o">::</span> <span class="kt">Tree</span>
<span class="n">root</span> <span class="o">=</span> <span class="kt">Object</span> <span class="p">[(</span><span class="s">"a"</span><span class="p">,</span> <span class="kt">Object</span> <span class="p">[(</span><span class="s">"b"</span><span class="p">,</span> <span class="kt">Object</span> <span class="p">[(</span><span class="s">"x"</span><span class="p">,</span> <span class="kt">Atom</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="s">"y"</span><span class="p">,</span> <span class="kt">Atom</span> <span class="mi">2</span><span class="p">)])])]</span>
</code></pre></div></div>

<h3 id="first-approach-root-based">First Approach: Root-based</h3>

<p>The first query <code class="language-plaintext highlighter-rouge">set .a.b.x = 42 | set .a.b.y = 43</code> can be implemented by accessing the target node and modifying it, then
accessing another target node through the modified root node and modifying it again.</p>

<p>To access a node, we recursively follow the path from the root to the target node and recursively create new nodes along
the way. The unchanged nodes are shared between the original tree and the new tree. The number of nodes that are
modified by the <code class="language-plaintext highlighter-rouge">access</code> function is <code class="language-plaintext highlighter-rouge">O(depth(node))</code>, where <code class="language-plaintext highlighter-rouge">depth(node)</code> is the depth of the target node in the tree.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">access</span> <span class="o">::</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span>
<span class="n">access</span> <span class="kt">[]</span> <span class="n">f</span> <span class="n">t</span> <span class="o">=</span> <span class="n">f</span> <span class="n">t</span>
<span class="n">access</span> <span class="p">(</span><span class="n">k</span> <span class="o">:</span> <span class="n">ks</span><span class="p">)</span> <span class="n">f</span> <span class="p">(</span><span class="kt">Object</span> <span class="n">ts</span><span class="p">)</span>
  <span class="o">|</span> <span class="kr">let</span> <span class="p">(</span><span class="n">before</span><span class="p">,</span> <span class="n">rest</span><span class="p">)</span> <span class="o">=</span> <span class="n">break</span> <span class="p">((</span><span class="o">==</span> <span class="n">k</span><span class="p">)</span> <span class="o">.</span> <span class="n">fst</span><span class="p">)</span> <span class="n">ts</span>
  <span class="p">,</span> <span class="p">((</span><span class="kr">_</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="o">:</span> <span class="n">after</span><span class="p">)</span> <span class="o">&lt;-</span> <span class="n">rest</span> <span class="o">=</span>
      <span class="kr">let</span> <span class="n">modifiedChild</span> <span class="o">=</span> <span class="n">access</span> <span class="n">ks</span> <span class="n">f</span> <span class="n">v</span>
       <span class="kr">in</span> <span class="kt">Object</span> <span class="p">(</span><span class="n">before</span> <span class="o">++</span> <span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">modifiedChild</span><span class="p">)</span> <span class="o">:</span> <span class="n">after</span><span class="p">)</span>
<span class="n">access</span> <span class="kr">_</span> <span class="kr">_</span> <span class="kr">_</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"Invalid path to access"</span>
</code></pre></div></div>

<p>When the path is empty, we apply the modification function f to the current node. Otherwise, we find the child with
key k, recurse into it with the remaining path, and rebuild the current node with the modified child. The cost is
O(depth(node)) new nodes per modification.</p>

<h4 id="example-of-access">Example of <code class="language-plaintext highlighter-rouge">access</code></h4>

<p>Suppose we modify the “x” node to 42 with <code class="language-plaintext highlighter-rouge">access ["a", "b", "x"] (const $ Atom 42) root</code>, the new tree and the original
tree would look like the following:</p>

<p><img src="/assets/posts/understanding-zippers/zippers-access-example.svg" alt="The original and updated trees share the unchanged y node" /></p>

<p>In the diagram, the <code class="language-plaintext highlighter-rouge">tree_a</code> stands for a <code class="language-plaintext highlighter-rouge">Tree</code> node that is a child of the root node with key “a”. The <code class="language-plaintext highlighter-rouge">tree_a'</code> is a
modified version of <code class="language-plaintext highlighter-rouge">tree_a</code> with the modified child node “x”. The <code class="language-plaintext highlighter-rouge">root'</code> is a modified version of the original root
node with modified nodes. So is <code class="language-plaintext highlighter-rouge">tree_b'</code>.</p>

<p>From the diagram, we can see that the modified node “x” is a new node with value 42, and its parent node “b” is also a
new node that shares the unchanged child node “y” with the original tree.</p>

<h4 id="query-execution">Query execution</h4>

<p>The whole query can be translated to the following Haskell code:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span> 
  <span class="n">access</span> <span class="p">[</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"y"</span><span class="p">]</span> <span class="p">(</span><span class="n">const</span> <span class="o">$</span> <span class="kt">Atom</span> <span class="mi">43</span><span class="p">)</span>
  <span class="o">.</span> <span class="n">access</span> <span class="p">[</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"x"</span><span class="p">]</span> <span class="p">(</span><span class="n">const</span> <span class="o">$</span> <span class="kt">Atom</span> <span class="mi">42</span><span class="p">)</span>
<span class="p">)</span>
  <span class="n">root</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">set .a.b.x = 42</code> is translated to <code class="language-plaintext highlighter-rouge">access ["a", "b", "x"] (const $ Atom 42)</code>, which evaluates to a function that takes a
tree and returns a new tree, and so on. The <code class="language-plaintext highlighter-rouge">|</code> operator is translated to function composition, which is <code class="language-plaintext highlighter-rouge">.</code> in Haskell.
The two <code class="language-plaintext highlighter-rouge">access</code> functions are composed together, and the resulting function is applied to the original tree <code class="language-plaintext highlighter-rouge">root</code> to
get the modified tree.</p>

<p>If there are <code class="language-plaintext highlighter-rouge">N</code> modifications in the query, the total number of nodes that are modified is <code class="language-plaintext highlighter-rouge">O(N * depth(tree))</code>. For
modifications that are close to each other, this can lead to a lot of redundant modifications. In our example, the “a”
and “b” nodes are modified twice, which is inefficient.</p>

<h3 id="second-approach-cursor-based">Second approach: cursor-based</h3>

<p>The query <code class="language-plaintext highlighter-rouge">at .a.b { set .x = 42 | set .y = 43 }</code> introduces a cursor that allows us to focus on a specific node in
the tree and execute a query with the focused node as the root node. The <code class="language-plaintext highlighter-rouge">at</code> query can be implemented by using
a technique called <strong>Zippers</strong>.</p>

<h4 id="zippers">Zippers</h4>

<p>Zippers are a powerful technique for navigating and modifying persistent data structures like trees. They allow us to go
to a parent node or to a specific child node in a much more efficient way, without needing to always go back to the root
node to access a node.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">Zipper</span> <span class="o">=</span> <span class="kt">Zipper</span>
  <span class="p">{</span> <span class="n">focus</span> <span class="o">::</span> <span class="kt">Tree</span>
  <span class="p">,</span> <span class="n">breadcrumbs</span> <span class="o">::</span> <span class="p">[</span><span class="kt">Crumb</span><span class="p">]</span>
  <span class="p">}</span>
  
<span class="kr">data</span> <span class="kt">Crumb</span> <span class="o">=</span> <span class="kt">Crumb</span>
  <span class="p">{</span> <span class="n">before</span> <span class="o">::</span> <span class="p">[(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">Tree</span><span class="p">)]</span>
  <span class="p">,</span> <span class="n">holeKey</span> <span class="o">::</span> <span class="kt">String</span>
  <span class="p">,</span> <span class="n">after</span> <span class="o">::</span> <span class="p">[(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">Tree</span><span class="p">)]</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>A <code class="language-plaintext highlighter-rouge">Zipper</code> consists of:</p>
<ul>
  <li>the <strong>currently focused tree node</strong></li>
  <li>a list of <strong>breadcrumbs</strong> that stores the path from the root to the current node.</li>
</ul>

<p>A <code class="language-plaintext highlighter-rouge">Crumb</code> looks like a <code class="language-plaintext highlighter-rouge">Tree</code>, except that one <code class="language-plaintext highlighter-rouge">Tree</code> has been removed; it is the node we most recently descended into. The <code class="language-plaintext highlighter-rouge">holeKey</code> stores the key of the removed node, <code class="language-plaintext highlighter-rouge">before</code> contains the preceding siblings, and <code class="language-plaintext highlighter-rouge">after</code>
contains the following siblings.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">emptyZipper</span> <span class="o">::</span> <span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">emptyZipper</span> <span class="n">t</span> <span class="o">=</span> <span class="kt">Zipper</span> <span class="n">t</span> <span class="kt">[]</span>
</code></pre></div></div>

<p>We create a Zipper focusing on the root:</p>

<p><img src="/assets/posts/understanding-zippers/zipper-at-root.svg" alt="A zipper focused on the root with an empty breadcrumb stack" /></p>

<p>In the diagram, the focus node has the same value as the original root node, and the breadcrumb stack is empty because we are at the root node.</p>

<h4 id="move-down">Move down</h4>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">goDown</span> <span class="o">::</span> <span class="kt">String</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">goDown</span> <span class="n">k</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="p">(</span><span class="kt">Object</span> <span class="n">ts</span><span class="p">)</span> <span class="n">bs</span><span class="p">)</span>
  <span class="o">|</span> <span class="p">(</span><span class="n">l</span><span class="p">,</span> <span class="p">(</span><span class="kr">_</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="o">:</span> <span class="n">r</span><span class="p">)</span> <span class="o">&lt;-</span> <span class="n">break</span> <span class="p">((</span><span class="o">==</span> <span class="n">k</span><span class="p">)</span> <span class="o">.</span> <span class="n">fst</span><span class="p">)</span> <span class="n">ts</span> <span class="o">=</span> <span class="kt">Zipper</span> <span class="n">v</span> <span class="p">(</span><span class="kt">Crumb</span> <span class="n">l</span> <span class="n">k</span> <span class="n">r</span> <span class="o">:</span> <span class="n">bs</span><span class="p">)</span>
<span class="n">goDown</span> <span class="n">k</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="n">f</span> <span class="kr">_</span><span class="p">)</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Cannot go to child '"</span> <span class="o">++</span> <span class="n">k</span> <span class="o">++</span> <span class="s">"' of tree: "</span> <span class="o">++</span> <span class="n">show</span> <span class="n">f</span>
</code></pre></div></div>

<p>Moving downward creates a <code class="language-plaintext highlighter-rouge">Crumb</code> from the parent node by extracting the target child, which becomes the new focus node.
The <code class="language-plaintext highlighter-rouge">Crumb</code> is then pushed onto the breadcrumb stack.</p>

<p>We go down to “a”, the Zipper would look like the following:</p>

<p><img src="/assets/posts/understanding-zippers/zipper-focused-on-a.svg" alt="The zipper after moving down to tree_a" /></p>

<p>In the diagram, the <code class="language-plaintext highlighter-rouge">crumb_0</code> is the top <code class="language-plaintext highlighter-rouge">Crumb</code> in the breadcrumb stack. The <code class="language-plaintext highlighter-rouge">holeKey</code> indicates that the “a” node is
taken away from the root node, and the <code class="language-plaintext highlighter-rouge">before</code> and <code class="language-plaintext highlighter-rouge">after</code> fields are empty because there is no sibling of “a”. The focus
node is identical to the “a” node in the original tree.</p>

<p>Then go down to “b”:</p>

<p><img src="/assets/posts/understanding-zippers/zipper-focused-on-b.svg" alt="The zipper after moving down to tree_b" /></p>

<p>The focus node is identical to the “b” in the original tree. The <code class="language-plaintext highlighter-rouge">crumb_1</code> looks similar to <code class="language-plaintext highlighter-rouge">tree_a</code>, but the “b” node
is taken away and replaced with a hole, which is indicated by the <code class="language-plaintext highlighter-rouge">holeKey</code> field. The <code class="language-plaintext highlighter-rouge">before</code> and <code class="language-plaintext highlighter-rouge">after</code> fields are
empty.</p>

<p>Now we go down to “x”, the Zipper would look like the following:</p>

<p><img src="/assets/posts/understanding-zippers/zipper-focused-on-x.svg" alt="The zipper after moving down to the x value" /></p>

<p>The newly added <code class="language-plaintext highlighter-rouge">crumb_2</code> indicates that the “x” node is taken away from the “b” node, and the “y” node is a sibling of
“x”, so it is stored in the <code class="language-plaintext highlighter-rouge">after</code> field of the <code class="language-plaintext highlighter-rouge">Crumb</code>. The focus node is identical to the “x” node in the original
tree.</p>

<h4 id="focus-modification">Focus modification</h4>

<p>Now we modify the value of “x” to 42. We just call the <code class="language-plaintext highlighter-rouge">modify</code> function on the focus node:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">modifyZipper</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">modifyZipper</span> <span class="n">f</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="n">t</span> <span class="n">bs</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Zipper</span> <span class="p">(</span><span class="n">f</span> <span class="n">t</span><span class="p">)</span> <span class="n">bs</span>
</code></pre></div></div>

<p>Modifying the focus node does not change the breadcrumbs, nor does it return a new root node. So the time complexity of
<code class="language-plaintext highlighter-rouge">modifyZipper</code> is <code class="language-plaintext highlighter-rouge">O(1)</code>.</p>

<p>We modify the “x” node to 42. Now in the zipper, the focus node is a new node with value 42.</p>

<h4 id="move-up">Move up</h4>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">goUp</span> <span class="o">::</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">goUp</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="n">t</span> <span class="p">(</span><span class="kt">Crumb</span> <span class="n">l</span> <span class="n">key</span> <span class="n">r</span> <span class="o">:</span> <span class="n">bs</span><span class="p">))</span> <span class="o">=</span> <span class="kt">Zipper</span> <span class="p">(</span><span class="kt">Object</span> <span class="p">(</span><span class="n">l</span> <span class="o">++</span> <span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">t</span><span class="p">)</span> <span class="o">:</span> <span class="n">r</span><span class="p">))</span> <span class="n">bs</span>
<span class="n">goUp</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="kr">_</span> <span class="kt">[]</span><span class="p">)</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"Already at the top"</span>
</code></pre></div></div>

<p>Moving upward is a reverse process of moving downward. It <strong>reassembles the tree</strong> by filling the hole in the Crumb with
the current focus node, popping the Crumb from the list of breadcrumbs, and making the reassembled tree the new focus
node.</p>

<p>Now we go up, the Zipper would look like the following:</p>

<p><img src="/assets/posts/understanding-zippers/zipper-after-go-up.svg" alt="The zipper after rebuilding tree_b with the modified x value" /></p>

<p>In the above diagram, the new focus node is created by filling the hole in the crumb with the modified “x” node that
has value 42. The new focus node shares the unchanged child node “y” with the original “b” node.</p>

<h4 id="access-with-zipper">access with Zipper</h4>

<p>Unlike <code class="language-plaintext highlighter-rouge">access</code> which returns a new root node, <code class="language-plaintext highlighter-rouge">accessZ</code> goes to the target node, applies the function to the focus
node, and then goes back to the same position in the tree. The number of nodes that are modified by <code class="language-plaintext highlighter-rouge">accessZ</code> is
<code class="language-plaintext highlighter-rouge">O(distance(node, cursor))</code>, where <code class="language-plaintext highlighter-rouge">distance(node, cursor)</code> is the depth of the target node from the current cursor
node. If the target node is close to the cursor node, the number of modified nodes is a much smaller number than
<code class="language-plaintext highlighter-rouge">O(depth(node))</code>, which is the number of modified nodes by <code class="language-plaintext highlighter-rouge">access</code>.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">accessZ</span> <span class="o">::</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">accessZ</span> <span class="kt">[]</span> <span class="n">f</span> <span class="n">z</span> <span class="o">=</span> <span class="n">f</span> <span class="n">z</span>
<span class="n">accessZ</span> <span class="p">(</span><span class="n">k</span> <span class="o">:</span> <span class="n">ks</span><span class="p">)</span> <span class="n">f</span> <span class="n">z</span> <span class="o">=</span> <span class="n">accessZ</span> <span class="n">ks</span> <span class="n">f</span> <span class="p">(</span><span class="n">goDown</span> <span class="n">k</span> <span class="n">z</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">goUp</span>

<span class="p">(</span><span class="o">&amp;</span><span class="p">)</span> <span class="o">::</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">b</span>
<span class="n">x</span> <span class="o">&amp;</span> <span class="n">f</span> <span class="o">=</span> <span class="n">f</span> <span class="n">x</span>
</code></pre></div></div>

<p>In the <code class="language-plaintext highlighter-rouge">accessZ</code> function, we first check if the path is empty. If it is, we apply the modification function <code class="language-plaintext highlighter-rouge">f</code> to the
current Zipper. If the path is not empty, we go down to the child node with key <code class="language-plaintext highlighter-rouge">k</code>, recursively call <code class="language-plaintext highlighter-rouge">accessZ</code> on the
child node with the remaining path <code class="language-plaintext highlighter-rouge">ks</code>, and then go back up to the original position.</p>

<p>The <code class="language-plaintext highlighter-rouge">(&amp;)</code> operator is a reverse function application operator, which allows us to write the operand before the function.
It is already defined in <code class="language-plaintext highlighter-rouge">Data.Function</code> in Haskell, but we define it here for completeness.</p>

<h4 id="at-implementation"><code class="language-plaintext highlighter-rouge">at</code> implementation</h4>

<p>We can implement the <code class="language-plaintext highlighter-rouge">at</code> query by using <code class="language-plaintext highlighter-rouge">accessZ</code> to navigate to the target node, apply the modification, and
then go back to the root node. The <code class="language-plaintext highlighter-rouge">withCursor</code> function takes a path to the target node, a modification function that
takes a Zipper and returns a modified Zipper, and the original tree. It returns a new tree with the modifications
applied.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">withCursor</span> <span class="o">::</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span>
<span class="n">withCursor</span> <span class="n">path</span> <span class="n">f</span> <span class="n">t</span> <span class="o">=</span> <span class="n">focus</span> <span class="o">$</span> <span class="n">accessZ</span> <span class="n">path</span> <span class="n">f</span> <span class="p">(</span><span class="n">emptyZipper</span> <span class="n">t</span><span class="p">)</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">accessWCursor</code> function is a helper function that works similarly to <code class="language-plaintext highlighter-rouge">access</code>, which allows us to modify a tree
node.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">accessWCursor</span> <span class="o">::</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="kt">Tree</span> <span class="o">-&gt;</span> <span class="kt">Tree</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span> <span class="o">-&gt;</span> <span class="kt">Zipper</span>
<span class="n">accessWCursor</span> <span class="n">path</span> <span class="n">f</span> <span class="o">=</span> <span class="n">accessZ</span> <span class="n">path</span> <span class="p">(</span><span class="n">modifyZipper</span> <span class="n">f</span><span class="p">)</span>
</code></pre></div></div>

<h4 id="query-execution-1">Query execution</h4>

<p>The second query <code class="language-plaintext highlighter-rouge">at .a.b { set .x = 42 | set .y = 43 }</code> will be translated to the following code:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">withCursor</span>
  <span class="p">[</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">]</span>
  <span class="p">(</span><span class="n">accessWCursor</span> <span class="p">[</span><span class="s">"y"</span><span class="p">]</span> <span class="p">(</span><span class="n">const</span> <span class="o">$</span> <span class="kt">Atom</span> <span class="mi">43</span><span class="p">)</span> <span class="o">.</span> <span class="n">accessWCursor</span> <span class="p">[</span><span class="s">"x"</span><span class="p">]</span> <span class="p">(</span><span class="n">const</span> <span class="o">$</span> <span class="kt">Atom</span> <span class="mi">42</span><span class="p">))</span>
  <span class="n">root</span>
</code></pre></div></div>

<p>It uses <code class="language-plaintext highlighter-rouge">withCursor</code> to navigate to the “b” node, then with “b” as the cursor, modifies the “x” node and the “y” node
with <code class="language-plaintext highlighter-rouge">accessWCursor</code>.</p>

<p>If there are <code class="language-plaintext highlighter-rouge">N</code> modifications whose average distance from the cursor is <code class="language-plaintext highlighter-rouge">m</code>, the total number of rebuilt nodes is
<code class="language-plaintext highlighter-rouge">O(N * m + depth(tree))</code>. When <code class="language-plaintext highlighter-rouge">m</code> is much smaller than <code class="language-plaintext highlighter-rouge">depth(tree)</code>, this is more efficient than the root-based
approach’s <code class="language-plaintext highlighter-rouge">O(N * depth(tree))</code>.</p>

<h3 id="comparing-the-two-approaches">Comparing the two approaches</h3>

<p>Let’s compare the two approaches in terms of the number of node allocations and time complexity, assuming that:</p>

<ol>
  <li>The child lookups and parent reconstructions take <code class="language-plaintext highlighter-rouge">O(1)</code> time</li>
  <li>The average walk distance from the cursor to the target node is <code class="language-plaintext highlighter-rouge">m</code>.</li>
</ol>

<table>
  <thead>
    <tr>
      <th>Approach</th>
      <th>Tree nodes rebuilt</th>
      <th>Time complexity</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Root-based <code class="language-plaintext highlighter-rouge">access</code></td>
      <td><code class="language-plaintext highlighter-rouge">O(N * depth(tree))</code></td>
      <td><code class="language-plaintext highlighter-rouge">O(N * depth(tree))</code></td>
    </tr>
    <tr>
      <td>Zippers</td>
      <td><code class="language-plaintext highlighter-rouge">O(N * m + depth(tree))</code></td>
      <td><code class="language-plaintext highlighter-rouge">O(N * m + depth(tree))</code></td>
    </tr>
  </tbody>
</table>

<h2 id="when-to-use-zippers">When to use Zippers</h2>

<p>Zippers are not universally better than root-based access. Their advantage depends on the access pattern.</p>

<h3 id="high-spatial-locality">High spatial locality</h3>

<p>When a query performs many modifications in the same region of the tree, zippers avoid redundant rebuilds of the path
from the root. In one high-locality Haskell tree-traversal benchmark, <a href="https://arxiv.org/abs/1908.10926">Performance Analysis of
Zippers</a> reported a speedup of up to 280% over its root-based implementation. The key factor is <strong>spatial locality</strong>: the closer the edits are to each other (and to the cursor),
the greater the benefit.</p>

<p>When modifications are scattered across unrelated parts of the tree, the zipper must navigate up and back down for each
one, and the overhead of creating crumbs on every step can make it <strong>slower</strong> than simply calling <code class="language-plaintext highlighter-rouge">access</code> from the root
each time.</p>

<h3 id="read-only-access">Read-only access</h3>

<p>Zipper navigation still allocates wrappers, and moving upward reconstructs parent nodes even when no value is modified.
For read-only lookups, this overhead is wasted. An alternative is to maintain a cache from paths to values alongside the
tree. Modifications update both the tree (via a zipper) and the cache; reads consult the cache directly without any
navigation.</p>

<p>For example, consider a query that is focused on <code class="language-plaintext highlighter-rouge">.a.b</code> but needs to read the value of its child <code class="language-plaintext highlighter-rouge">.x</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>at .a.b {
  set .y = get .x + 1
}
</code></pre></div></div>

<p>The read can be resolved by computing the absolute path <code class="language-plaintext highlighter-rouge">["a", "b", "x"]</code> and looking it up in a cache, avoiding
zipper navigation entirely.</p>

<h3 id="rule-of-thumb">Rule of thumb</h3>

<table>
  <thead>
    <tr>
      <th>Scenario</th>
      <th>Preferred approach</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Many edits clustered in one subtree</td>
      <td>Zipper</td>
    </tr>
    <tr>
      <td>Edits scattered across the tree</td>
      <td>Root-based <code class="language-plaintext highlighter-rouge">access</code></td>
    </tr>
    <tr>
      <td>Read-only lookups</td>
      <td>Cache lookup / direct path lookup</td>
    </tr>
  </tbody>
</table>

<h2 id="complexity-of-zipper-implementation">Complexity of Zipper implementation</h2>

<p>Zipper implementation can have a lot of boilerplate code, especially when the tree structure is complex. For example, if
we have the following Value tree:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">Value</span> <span class="o">=</span> <span class="kt">Atom</span> <span class="kt">Int</span>
         <span class="o">|</span> <span class="kt">List</span> <span class="p">[</span><span class="kt">Value</span><span class="p">]</span>
         <span class="o">|</span> <span class="kt">Map</span> <span class="p">[(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">Value</span><span class="p">)]</span>
         <span class="o">|</span> <span class="kt">BinOp</span> <span class="kt">String</span> <span class="kt">Value</span> <span class="kt">Value</span>
         <span class="o">|</span> <span class="kt">UnOp</span> <span class="kt">String</span> <span class="kt">Value</span>
</code></pre></div></div>

<p>For each type of node that can have children, we need to define a complicated <code class="language-plaintext highlighter-rouge">Crumb</code>:</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">ValueCrumb</span> <span class="o">=</span> <span class="kt">ListCrumb</span> <span class="kt">Int</span> <span class="p">[</span><span class="kt">Value</span><span class="p">]</span> <span class="p">[</span><span class="kt">Value</span><span class="p">]</span>
                <span class="o">|</span> <span class="kt">MapCrumb</span> <span class="kt">String</span> <span class="p">[(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">Value</span><span class="p">)]</span> <span class="p">[(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">Value</span><span class="p">)]</span>
                <span class="o">|</span> <span class="kt">BinOpLeftCrumb</span> <span class="kt">String</span> <span class="kt">Value</span>
                <span class="o">|</span> <span class="kt">BinOpRightCrumb</span> <span class="kt">String</span> <span class="kt">Value</span>
                <span class="o">|</span> <span class="kt">UnOpCrumb</span> <span class="kt">String</span>
</code></pre></div></div>

<p>We will talk about how to reduce the boilerplate code in the next post.</p>

<h2 id="conclusion">Conclusion</h2>

<p>From an imperative-programming point of view, zippers can feel natural as they let us navigate and modify a tree node
“in place”, as opposed to the root-based approach where we always have to create a new tree for each modification.
Zippers work best when edits are clustered - with a cursor near the action, each modification costs only the distance
from the cursor instead of the full depth of the tree.</p>

<p>However, they are not a universal replacement for root-based access. Scattered edits gain little from a zipper, and
read-only lookups are better served by a flat index. The right choice depends on the access pattern.</p>

<h2 id="further-reading">Further reading</h2>

<ul>
  <li><a href="https://learnyouahaskell.github.io/zippers.html">Learn You a Haskell for Great Good!</a></li>
</ul>]]></content><author><name>Junxiang Zhou</name></author><category term="Haskell" /><summary type="html"><![CDATA[When working with tree-structured data, we often need to navigate to a specific node and modify it. In imperative languages, this is usually straightforward thanks to mutable state and parent pointers. In functional languages, however, immutability makes this pattern less obvious.]]></summary></entry></feed>