<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Dominic's Website</title><id>https://dominicm.dev/feed.xml</id><subtitle>Recent Posts</subtitle><updated>2026-01-21T23:37:13Z</updated><link href="https://dominicm.dev/feed.xml" rel="self" /><link href="https://dominicm.dev" /><entry><title>Applying Zettelkasten to programming</title><id>https://dominicm.dev/applying-zettelkasten-to-programming.html</id><author><name>Dominic Martinez</name><email>dom@dominicm.dev</email></author><updated>2025-06-08T12:00:00Z</updated><link href="https://dominicm.dev/applying-zettelkasten-to-programming.html" rel="alternate" /><content type="html">&lt;p&gt;As any programmer will tell you, code organization is paramount for any
non-trivial project. When our code grows too big for a single file, we split it
up into multiple ones. When we have too many files, we put them into folders.
When we have too many folders, we put them in &lt;em&gt;more&lt;/em&gt; folders ad infinitum. This
hierarchy strongly shapes a project's structure and maintainability.&lt;/p&gt;&lt;p&gt;Tree hierarchies feel quite natural. They conceptually fit with abstraction,
where parent modules abstract the capabilities of their children. It's also easy
to implement, leveraging the filesystem for most of the work. But how does it
work in practice?&lt;/p&gt;&lt;h2&gt;Siblings and permissions&lt;/h2&gt;&lt;p&gt;Having private utilities is a core piece of encapsulation; we carefully control the surface area exposed to the public. If a single module needs a private utility this is fairly straightforward, but often we need multiple modules to have access to the same internal operations. In basically every language, we end up doing this by creating a semi-public sibling module.&lt;/p&gt;&lt;p&gt;&lt;svg id=&quot;mermaid-svg&quot; width=&quot;100%&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; class=&quot;flowchart&quot; style=&quot;max-width: 243.234px; background-color: transparent;&quot; viewBox=&quot;-5.203000068664551 0 243.2342529296875 278&quot; role=&quot;graphics-document document&quot; aria-roledescription=&quot;flowchart-v2&quot;&gt;&lt;style&gt;#mermaid-svg{font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg .error-icon{fill:#552222;}#mermaid-svg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg .marker{fill:#333333;stroke:#333333;}#mermaid-svg .marker.cross{stroke:#333333;}#mermaid-svg svg{font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg p{margin:0;}#mermaid-svg .label{font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;color:#333;}#mermaid-svg .cluster-label text{fill:#333;}#mermaid-svg .cluster-label span{color:#333;}#mermaid-svg .cluster-label span p{background-color:transparent;}#mermaid-svg .label text,#mermaid-svg span{fill:#333;color:#333;}#mermaid-svg .node rect,#mermaid-svg .node circle,#mermaid-svg .node ellipse,#mermaid-svg .node polygon,#mermaid-svg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg .rough-node .label text,#mermaid-svg .node .label text,#mermaid-svg .image-shape .label,#mermaid-svg .icon-shape .label{text-anchor:middle;}#mermaid-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg .rough-node .label,#mermaid-svg .node .label,#mermaid-svg .image-shape .label,#mermaid-svg .icon-shape .label{text-align:center;}#mermaid-svg .node.clickable{cursor:pointer;}#mermaid-svg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg .arrowheadPath{fill:#333333;}#mermaid-svg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg .cluster text{fill:#333;}#mermaid-svg .cluster span{color:#333;}#mermaid-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg rect.text{fill:none;stroke-width:0;}#mermaid-svg .icon-shape,#mermaid-svg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg .icon-shape p,#mermaid-svg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg .icon-shape rect,#mermaid-svg .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg :root{--mermaid-font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-pointEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-pointStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;4.5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 5 L 10 10 L 10 0 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-circleEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;11&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-circleStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-1&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-crossEnd&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;12&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 2; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-crossStart&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;-1&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 2; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;g class=&quot;root&quot;&gt;&lt;g class=&quot;clusters&quot;/&gt;&lt;g class=&quot;edgePaths&quot;&gt;&lt;path d=&quot;M36.612,62L30.976,66.167C25.34,70.333,14.069,78.667,8.433,91.5C2.797,104.333,2.797,121.667,2.797,139C2.797,156.333,2.797,173.667,7.897,186.104C12.997,198.541,23.196,206.081,28.296,209.852L33.396,213.622&quot; id=&quot;L_A_util_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A_util_0&quot; data-points=&quot;W3sieCI6MzYuNjEyMjI5NTY3MzA3NjksInkiOjYyfSx7IngiOjIuNzk2ODc1LCJ5Ijo4N30seyJ4IjoyLjc5Njg3NSwieSI6MTM5fSx7IngiOjIuNzk2ODc1LCJ5IjoxOTF9LHsieCI6MzYuNjEyMjI5NTY3MzA3NjksInkiOjIxNn1d&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M73.133,62L73.133,66.167C73.133,70.333,73.133,78.667,73.133,86.333C73.133,94,73.133,101,73.133,104.5L73.133,108&quot; id=&quot;L_A_B_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A_B_0&quot; data-points=&quot;W3sieCI6NzMuMTMyODEyNSwieSI6NjJ9LHsieCI6NzMuMTMyODEyNSwieSI6ODd9LHsieCI6NzMuMTMyODEyNSwieSI6MTEyfV0=&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M136.021,62L145.725,66.167C155.43,70.333,174.84,78.667,184.545,86.333C194.25,94,194.25,101,194.25,104.5L194.25,108&quot; id=&quot;L_A_C_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A_C_0&quot; data-points=&quot;W3sieCI6MTM2LjAyMDU4MjkzMjY5MjMyLCJ5Ijo2Mn0seyJ4IjoxOTQuMjUsInkiOjg3fSx7IngiOjE5NC4yNSwieSI6MTEyfV0=&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M73.133,166L73.133,170.167C73.133,174.333,73.133,182.667,73.133,190.333C73.133,198,73.133,205,73.133,208.5L73.133,212&quot; id=&quot;L_B_util_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_B_util_0&quot; data-points=&quot;W3sieCI6NzMuMTMyODEyNSwieSI6MTY2fSx7IngiOjczLjEzMjgxMjUsInkiOjE5MX0seyJ4Ijo3My4xMzI4MTI1LCJ5IjoyMTZ9XQ==&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M194.25,166L194.25,170.167C194.25,174.333,194.25,182.667,181.381,192.359C168.512,202.05,142.773,213.101,129.904,218.626L117.035,224.151&quot; id=&quot;L_C_util_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_C_util_0&quot; data-points=&quot;W3sieCI6MTk0LjI1LCJ5IjoxNjZ9LHsieCI6MTk0LjI1LCJ5IjoxOTF9LHsieCI6MTEzLjM1OTM3NSwieSI6MjI1LjcyOTI3ODIwNDIxODUzfV0=&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabels&quot;&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A_util_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A_B_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A_C_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_B_util_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_C_util_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;nodes&quot;&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-A-0&quot; transform=&quot;translate(73.1328125, 35)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-65.1328125&quot; y=&quot;-27&quot; width=&quot;130.265625&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-35.1328125, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;70.265625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;A (parent)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-util-1&quot; transform=&quot;translate(73.1328125, 243)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-40.2265625&quot; y=&quot;-27&quot; width=&quot;80.453125&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-10.2265625, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;20.453125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;util&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-B-2&quot; transform=&quot;translate(73.1328125, 139)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-35.3359375&quot; y=&quot;-27&quot; width=&quot;70.671875&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-5.3359375, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;10.671875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;B&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-C-3&quot; transform=&quot;translate(194.25, 139)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-35.78125&quot; y=&quot;-27&quot; width=&quot;71.5625&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-5.78125, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;11.5625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;C&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/p&gt;&lt;p&gt;This causes two problems:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;util&lt;/code&gt; is now public to the rest of our application&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The folder structure no longer represents our architecture&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;While this simple example is still understandable, over time this conflict leads to gradual erosion of any correspondence between &lt;em&gt;where&lt;/em&gt; code is and &lt;em&gt;how&lt;/em&gt; it's being used. Look in nearly any codebase, and you will see imports traversing up &amp;amp; down to distant cousins in the folder tree.&lt;/p&gt;&lt;p&gt;While there are some clunky attempts to implement more granular permissions (e.g. Java's &lt;code&gt;protected&lt;/code&gt;), generally you have to choose between being used by one module, or being public.&lt;/p&gt;&lt;figure&gt;&lt;p&gt;&lt;svg id=&quot;mermaid-svg&quot; width=&quot;100%&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; class=&quot;flowchart&quot; style=&quot;max-width: 228.234px; background-color: transparent;&quot; viewBox=&quot;0 0 228.234375 174&quot; role=&quot;graphics-document document&quot; aria-roledescription=&quot;flowchart-v2&quot;&gt;&lt;style&gt;#mermaid-svg{font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg .error-icon{fill:#552222;}#mermaid-svg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg .marker{fill:#333333;stroke:#333333;}#mermaid-svg .marker.cross{stroke:#333333;}#mermaid-svg svg{font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg p{margin:0;}#mermaid-svg .label{font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;color:#333;}#mermaid-svg .cluster-label text{fill:#333;}#mermaid-svg .cluster-label span{color:#333;}#mermaid-svg .cluster-label span p{background-color:transparent;}#mermaid-svg .label text,#mermaid-svg span{fill:#333;color:#333;}#mermaid-svg .node rect,#mermaid-svg .node circle,#mermaid-svg .node ellipse,#mermaid-svg .node polygon,#mermaid-svg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg .rough-node .label text,#mermaid-svg .node .label text,#mermaid-svg .image-shape .label,#mermaid-svg .icon-shape .label{text-anchor:middle;}#mermaid-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg .rough-node .label,#mermaid-svg .node .label,#mermaid-svg .image-shape .label,#mermaid-svg .icon-shape .label{text-align:center;}#mermaid-svg .node.clickable{cursor:pointer;}#mermaid-svg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg .arrowheadPath{fill:#333333;}#mermaid-svg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg .cluster text{fill:#333;}#mermaid-svg .cluster span{color:#333;}#mermaid-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg rect.text{fill:none;stroke-width:0;}#mermaid-svg .icon-shape,#mermaid-svg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg .icon-shape p,#mermaid-svg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg .icon-shape rect,#mermaid-svg .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg :root{--mermaid-font-family:&quot;trebuchet ms&quot;,verdana,arial,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-pointEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-pointStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;4.5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 5 L 10 10 L 10 0 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-circleEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;11&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-circleStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-1&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 1; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-crossEnd&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;12&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 2; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-svg_flowchart-v2-crossStart&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;-1&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width: 2; stroke-dasharray: 1, 0;&quot;/&gt;&lt;/marker&gt;&lt;g class=&quot;root&quot;&gt;&lt;g class=&quot;clusters&quot;/&gt;&lt;g class=&quot;edgePaths&quot;&gt;&lt;path d=&quot;M43.397,62L41.794,66.167C40.192,70.333,36.986,78.667,36.059,86.345C35.132,94.024,36.483,101.048,37.158,104.56L37.834,108.072&quot; id=&quot;L_A_C_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A_C_0&quot; data-points=&quot;W3sieCI6NDMuMzk2NjM0NjE1Mzg0NjEsInkiOjYyfSx7IngiOjMzLjc4MTI1LCJ5Ijo4N30seyJ4IjozOC41ODg5NDIzMDc2OTIzMSwieSI6MTEyfV0=&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M89.117,61.124L94.951,65.437C100.784,69.749,112.451,78.375,122.613,86.419C132.776,94.463,141.436,101.926,145.765,105.657L150.095,109.389&quot; id=&quot;L_A_D_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A_D_0&quot; data-points=&quot;W3sieCI6ODkuMTE3MTg3NSwieSI6NjEuMTI0MTgwODI4NjEyNjh9LHsieCI6MTI0LjExNzE4NzUsInkiOjg3fSx7IngiOjE1My4xMjQ4NDk3NTk2MTU0LCJ5IjoxMTJ9XQ==&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M139.117,61.124L133.284,65.437C127.451,69.749,115.784,78.375,105.621,86.419C95.458,94.463,86.799,101.926,82.469,105.657L78.14,109.389&quot; id=&quot;L_B_C_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_B_C_0&quot; data-points=&quot;W3sieCI6MTM5LjExNzE4NzUsInkiOjYxLjEyNDE4MDgyODYxMjY4fSx7IngiOjEwNC4xMTcxODc1LCJ5Ijo4N30seyJ4Ijo3NS4xMDk1MjUyNDAzODQ2MSwieSI6MTEyfV0=&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;path d=&quot;M184.838,62L186.44,66.167C188.043,70.333,191.248,78.667,192.175,86.345C193.102,94.024,191.752,101.048,191.076,104.56L190.401,108.072&quot; id=&quot;L_B_D_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_B_D_0&quot; data-points=&quot;W3sieCI6MTg0LjgzNzc0MDM4NDYxNTQsInkiOjYyfSx7IngiOjE5NC40NTMxMjUsInkiOjg3fSx7IngiOjE4OS42NDU0MzI2OTIzMDc2OCwieSI6MTEyfV0=&quot; marker-end=&quot;url(#mermaid-svg_flowchart-v2-pointEnd)&quot;/&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabels&quot;&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A_C_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A_D_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_B_C_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_B_D_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;nodes&quot;&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-A-0&quot; transform=&quot;translate(53.78125, 35)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-35.3359375&quot; y=&quot;-27&quot; width=&quot;70.671875&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-5.3359375, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;10.671875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;A&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-C-1&quot; transform=&quot;translate(43.78125, 139)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-35.78125&quot; y=&quot;-27&quot; width=&quot;71.5625&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-5.78125, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;11.5625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;C&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-D-3&quot; transform=&quot;translate(184.453125, 139)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-35.78125&quot; y=&quot;-27&quot; width=&quot;71.5625&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-5.78125, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;11.5625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;D&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;flowchart-B-4&quot; transform=&quot;translate(174.453125, 35)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-35.3359375&quot; y=&quot;-27&quot; width=&quot;70.671875&quot; height=&quot;54&quot;/&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-5.3359375, -12)&quot;&gt;&lt;rect/&gt;&lt;foreignObject width=&quot;10.671875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;B&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/p&gt;&lt;figcaption&gt;&lt;p&gt;Representing a dependency structure like this is not generally possible&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2&gt;The first casualty: tests&lt;/h2&gt;&lt;p&gt;This limitation has caused a long-standing debate in the unit test community: how do you run tests, which are in a separate module, against private functions? Currently, your options usually are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Don't test private functions; only test public interfaces&lt;/li&gt;&lt;li&gt;Publicize all functions, but with naming conventions that indicate they are actually private&lt;/li&gt;&lt;li&gt;Write tests in the same module as code&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;To be straightforward, option 1 is flat-out incorrect. The point of unit tests is to make sure that each functional unit in your application behaves as expected under a wide variety of scenarios. If you are only testing a module's public interface, you are writing integration tests. Being able to test private edge cases and have a well-defined purpose for private functions leads to higher confidence that your public interface is actually robust. This is an &amp;quot;actually it's a skill issue&amp;quot; justification for people struggling against the very real limitations of our programming paradigms.&lt;/p&gt;&lt;p&gt;I hope it's also clear that patterns like &lt;code&gt;public _myPrivateFunction&lt;/code&gt; and &lt;code&gt;if (&amp;lt;inTestRunner&amp;gt;) { ... }&lt;/code&gt; are hacks around design limitations, not actual solutions.&lt;/p&gt;&lt;h2&gt;What does Zettelkasten have to do with anything?&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Zettelkasten&quot;&gt;Zettelkasten&lt;/a&gt; is a knowledge management system that organizes things through links, rather than locations. Instead of trying to constantly move information around in a hierarchical system, you divide notes into small, atomic nodes and use their links to other nodes for discovery.&lt;/p&gt;&lt;p&gt;This is surprisingly similar to our requirements for programming! We have a series of atomic nodes (functions) that can arbitrarily link to each other. Instead of code living in a single location, its purpose is organically derived from how it's used and documented.&lt;/p&gt;&lt;p&gt;We actually somewhat follow this pattern today! Monorepos encapsulate code in packages with flexible dependencies, hinting at a node-based approach. This idea is an extension of that to all code, further reducing the overhead of manual organization.&lt;/p&gt;&lt;p&gt;The key for this to work is a good user interface that lets you have dynamic &amp;quot;views&amp;quot; of code, such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Show me this function and all functions it uses within the current module&lt;/li&gt;&lt;li&gt;Show me all the code relevant to this stack trace&lt;/li&gt;&lt;li&gt;Show me all the consumers of this function&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Parting thoughts&lt;/h2&gt;&lt;p&gt;This is the first in an upcoming series of posts describing some of the design principles of an in-progress programming environment. The core to everything here is that we need to start treating code in a way conducive to large-scale management and understanding, not in a way that fits into our decades-old general computing techniques.&lt;/p&gt;</content></entry><entry><title>Technology sucks. Why?</title><id>https://dominicm.dev/why-does-technology-suck.html</id><author><name>Dominic Martinez</name><email>dom@dominicm.dev</email></author><updated>2024-03-04T14:27:00Z</updated><link href="https://dominicm.dev/why-does-technology-suck.html" rel="alternate" /><content type="html">&lt;p&gt;I live and breathe technology. I program for work and for fun. I play games,
watch shows, and spend time with friends all on my computer. Technology is an
incredible innovation that has unlocked a range of new activities and
possibilities, quickly becoming something we rely on every day.&lt;/p&gt;&lt;p&gt;It's also, in many ways, a giant piece of crap.&lt;/p&gt;&lt;h2&gt;Part one: I'm not being melodramatic, I swear&lt;/h2&gt;&lt;p&gt;I am admittedly a bit biased due to my intimate connection to technology, but I
think the tech industry is failing in ways I don't see in other industries. I
encounter significant bugs in major pieces of software like Google Maps,
Android, or Windows on a daily basis, and many large tech companies debug and
patch multiple user-facing issues per day. Software is regularly abandoned or
completely rewritten due to tech debt, and upgrades often make things slower and
less usable rather than the opposite.&lt;/p&gt;&lt;p&gt;I'm not talking about the typical small-business website made on a tight budget.
Those are admittedly bad, and there are ways we can improve them, but low
quality in projects like that is common across industries. I'm talking about
companies with thousands of crazy smart, talented, full-time engineers who are
struggling to make something good.&lt;/p&gt;&lt;p&gt;Why are we stuck with a few pieces of good software and a frustratingly bad
majority? The industry is certainly paying enough to attract necessary talent.
It's not like software companies are uniquely bad at making things either; the
worst part of pretty much any modern car is generally the infotainment system!
It seems to just be software itself that's the issue. Why?&lt;/p&gt;&lt;h2&gt;Part two: What went wrong?&lt;/h2&gt;&lt;aside class=&quot;inline info&quot;&gt;&lt;h2&gt;Disclaimer&lt;/h2&gt;&lt;p&gt;Everything here is my personal opinion, with no real research backing it up.
Take this with the appropriate dose of salt.&lt;/p&gt;&lt;/aside&gt;&lt;h3&gt;Software is abstract&lt;/h3&gt;&lt;p&gt;Unlike in mechanical engineering where the product is a physical object,
software is abstract. I believe this is actually a major barrier to creating
software at scale.&lt;/p&gt;&lt;p&gt;Humans have evolved to be masters of visual pattern recognition, and the
physicality of mechanical engineering provides a source of truth for what you're
creating. Where is each part? What parts are interacting? Where are likely
failure points? How does the system work at a high level? It's orders of
magnitude quicker to get this information from a 3D model of a part versus
carefully reading a codebase.&lt;/p&gt;&lt;p&gt;This abstractness makes code much more opaque. We use abstractions to solidify
this opacity in logical places, but in a company setting it's very easy to
create poor, undocumented, or incorrect abstractions. Fundamentally, software
engineers are operating with less information and a poorer understanding of
their work.&lt;/p&gt;&lt;p&gt;Complex, abstract work isn't limited to software engineering. I think a lot of
similarity can be drawn between a mathematical paper and a piece of software.
Both have &amp;quot;hard&amp;quot; bugs, where an oversight leads to a result that's just plain
wrong. Both are abstract, and take a significant amount of time to audit and
understand. Where software engineering becomes unique is in its scale, both in
the amount of code and the number of people working on it at once.&lt;/p&gt;&lt;h3&gt;Software is relatively unconstrained&lt;/h3&gt;&lt;p&gt;There's a ton of constraints when building physical objects. Every new part
costs money up front and per-unit, so there's a very strong incentive to
minimize complexity and use pre-existing parts. And of course, the design itself
is limited by reality; you can't just build a stove that floats or have your
fridge magically cool your bathroom.&lt;/p&gt;&lt;p&gt;By comparison, software has much fewer constraints, both from a technical and
economical perspective. The cost of a programmer is so enormous compared to any
running costs of the software itself that extra complexity and inefficiencies
are easy to justify. Technically, any part of a piece of software can interact
with any other piece.&lt;/p&gt;&lt;p&gt;This freedom is one of the strengths of software! It's how we can iterate
quickly and have such creativity. But it also means that, left to grow
organically, software becomes overly complex and interconnected. In tandem with
its abstract nature, large pieces of software are fragile and
difficult to understand.&lt;/p&gt;&lt;h3&gt;Best practices aren't enough&lt;/h3&gt;&lt;p&gt;In a perfect world, we have best practices and architectures to deal with the
above issues. With enough work and thought, we can create clean and maintainable
software.&lt;/p&gt;&lt;p&gt;The world is, surprise, rarely perfect, and discipline rarely beats out
structural incentives. Engineers with the best of intentions will make mistakes,
and over time as best practices become more difficult to maintain, discipline
will erode. Even if each individual change is well-designed, their sum will
eventually corrode the quality of the code. As the system becomes more complex,
the understanding needed to develop an intelligent architecture also becomes
harder to obtain.&lt;/p&gt;&lt;p&gt;Putting effort into developing a better piece of software is important, but our
systems are not supporting it at scale.&lt;/p&gt;&lt;h3&gt;Mistakes are cheap (until they aren't)&lt;/h3&gt;&lt;p&gt;Mistakes in software are among the cheapest to fix. You don't have to do a
product recall or change manufacturing; you just push out an update. As a
result, you don't generally need to take as much care when developing software.
A fix is always a rollback away.&lt;/p&gt;&lt;p&gt;That is, &lt;em&gt;if&lt;/em&gt; the mistake is a bug.&lt;/p&gt;&lt;p&gt;A bug is relatively obvious; the program is not doing what it's supposed to. But
we make other mistakes too. We may architect a program incorrectly, not
anticipate a future use case, or simply make tradeoffs that were reasonable at
the time but cause issues later down the road.&lt;/p&gt;&lt;p&gt;These mistakes usually don't rear their head for years or decades, and once they
do, they are extremely hard to remove. It's why Windows is still compatible with
DOS, why memory safety vulnerabilities are so prevalent, and why the mainstream
way to create a cross-platform application is to embed it in its own
mini-operating system (read: a web browser).&lt;/p&gt;&lt;p&gt;Software builds on software in a way that's extremely costly to change, possibly
even more than in manufacturing. This is a dire enough problem on its own, but
combined with the apparent cheapness of fixing software, we are lulled into a
false sense of complacency with lackadaisical software development. Costly
mistakes are introduced at a rate that's not feasible to deal with continuously,
and so the spectre of tech debt begins to haunt the files until someone takes a
flamethrower to the whole thing.&lt;/p&gt;&lt;h3&gt;We live in a society&lt;/h3&gt;&lt;p&gt;And finally, sometimes building good software just isn't the goal. Almost all
software we use is commercial, and the end goal is to extract value. If a
crappier product makes more money, then the crappier product &lt;em&gt;will&lt;/em&gt; win in a
commercial setting. Software also has powerful monopolistic properties that mean
it can be cheaper to just force people to keep using your product rather than
fixing it.&lt;/p&gt;&lt;p&gt;This topic deserves its own blog post, but needless to say, if making really
good software is hard (and it is), companies will find ways to make money from
software without focusing on quality.&lt;/p&gt;&lt;h2&gt;Part three: So what do we do?&lt;/h2&gt;&lt;p&gt;¯\_(ツ)_/¯&lt;/p&gt;&lt;p&gt;Underneath the technical details, this is mostly one of those annoyingly
intractable &amp;quot;human&amp;quot; problems (ew). It’s very difficult to move the mountains
that are large tech corporations in a meaningful way.&lt;/p&gt;&lt;p&gt;If you work somewhere that you feel is falling into this trap of bad software,
the best thing you can do is act like it's an important issue every step of the
way. Be curious, talk to people, and figure out how to break down silos. Dogfood
the product, give feedback, and make sure things actually get fixed. Be loud
about why quality is important in ways that maybe aren't immediately obvious on
an A/B test. Make it a product problem, not an engineering wishlist.&lt;/p&gt;</content></entry><entry><title>Going beyond the REPL</title><id>https://dominicm.dev/going-beyond-the-repl.html</id><author><name>Dominic Martinez</name><email>dom@dominicm.dev</email></author><updated>2023-11-11T12:05:00Z</updated><link href="https://dominicm.dev/going-beyond-the-repl.html" rel="alternate" /><content type="html">&lt;aside class=&quot;inline info&quot;&gt;&lt;h2&gt;Disclaimer&lt;/h2&gt;&lt;p&gt;This post is currently rambling a bit. I'll try and make it more concise.&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;REPL-driven development has
&lt;a href=&quot;https://mikelevins.github.io/posts/2020-12-18-repl-driven/&quot;&gt;long&lt;/a&gt;
&lt;a href=&quot;https://web.archive.org/web/20110112075449/http://programming-musings.org/2006/01/14/the-joy-of-repl&quot;&gt;been&lt;/a&gt;
&lt;a href=&quot;http://blog.jayfields.com/2014/01/repl-driven-development.html&quot;&gt;lauded&lt;/a&gt; by Lisp
programmers, but has failed to go mainstream. Can we design a better approach to
interactive development?&lt;/p&gt;&lt;h2&gt;The problems with REPLs&lt;/h2&gt;&lt;h3&gt;Separation of the REPL and code&lt;/h3&gt;&lt;p&gt;REPL-driven development generally involves testing expressions in a REPL, and
then transferring them to code when completed. The state of your REPL and code
are completely disconnected. Some common issues here are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Changing a function in code and forgetting to change it in the REPL&lt;/li&gt;&lt;li&gt;Using a variable with different values in the code vs the REPL&lt;/li&gt;&lt;li&gt;Using a function or variable that no longer exists in the code&lt;/li&gt;&lt;li&gt;Needing to emulate the state of the program at the point an expression is run&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Ephemeral knowledge&lt;/h3&gt;&lt;p&gt;Exploratory development via a REPL can be incredibly empowering. Rather than
guessing-and-checking via print statements, you can interactively test any
expression.&lt;/p&gt;&lt;p&gt;But once you've finished your coding session, what happens? You've built
knowledge on how all the expressions in your function operate, and after closing
your REPL window…it all disappears. We just throw it away.&lt;/p&gt;&lt;p&gt;By giving the developer powerful introspection abilities but not the reader,
REPLs create a rift in understanding. Questions that are trivially answered in
the developer's state of mind are lost to the reader because they don't have the
same context.&lt;/p&gt;&lt;p&gt;Code written without a REPL is clearer to the reader by necessity, because the
information the developer and reader are working with is more similar.&lt;/p&gt;&lt;h2&gt;What about debuggers?&lt;/h2&gt;&lt;p&gt;Debuggers are great, but they don't replace REPLs. Testing an arbitrary
expression requires writing it and re-running the entire program to a breakpoint.
You then inspect the value. This is too slow for REPL-driven development; we
need to incrementally update the program state.&lt;/p&gt;&lt;h2&gt;What about computational notebooks?&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Notebook_interface&quot;&gt;Computational notebooks&lt;/a&gt; have
gained mainstream support, primarily due to their REPL-like structure.
Unfortunately, they are generally single-file programs meant to run
interactively, and are too narrow in scope to serve as a general-purpose
programming technique. Computational notebooks do, however, hint at the
direction we need to go.&lt;/p&gt;&lt;h2&gt;The idea&lt;/h2&gt;&lt;p&gt;Regardless of what language you're in, there's always &lt;em&gt;some&lt;/em&gt; way to inspect an
expression. The problem is not can we test an expression, but how fast we can do
so. REPLs test small expressions quickly, but introduce a friction boundary
between the REPL and code. Can we test small changes directly in the code,
rather than in a REPL?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;f x = out
  where
    a = op1 x
    b = op2 a
    out = some_complex_function b

-- We need to know what b is first!
-- Copying all the previous expressions is tedious
&amp;gt;&amp;gt; a = op1 &amp;quot;my input&amp;quot;
&amp;gt;&amp;gt; b = op2 a
&amp;gt;&amp;gt; some_complex_function d
&amp;quot;my answer&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Technologically, this is not an interesting idea; debuggers already do this. The
point is developing a UX that encourages a healthier developer workflow.&lt;/p&gt;&lt;p&gt;How many times have you written an entire function, file, or more before ever
running any code? How often were you unsure if an expression was correct, but
delayed testing it until the function was complete? The harder it is to inspect
and test code, the longer we will wait until testing it.&lt;/p&gt;&lt;p&gt;We need to put the value of an expression right in your face, as soon as you
write it. &lt;strong&gt;Let's evaluate our code continuously, and show the values of
expressions as soon as you type them&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;As a developer, this makes the feedback between writing and testing code
instant. There's no explicit action to take to start a test, and so no incentive
to wait to test. If an expression is incorrect on the given test case, the
developer will see that immediately after writing it.&lt;/p&gt;&lt;p&gt;As a reader, this is a powerful tool for exploring an unfamiliar codebase.
Learning by example is a powerful tool, and seeing concrete instances of any
value lets readers think less abstractly about code.&lt;/p&gt;&lt;h3&gt;Leveraging unit tests&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;/assets/images/going-beyond-the-repl/live-expression-example.png&quot; alt=&quot;An example function with the values of all intermediate expressions shown on screen based on a unit test&quot; /&gt;&lt;/p&gt;&lt;p&gt;We can use unit tests to run our desired function. Not only does this give the reader
documented examples to introspect, but it also encourages developing unit tests
in parallel with function development.&lt;/p&gt;&lt;h3&gt;Loops &amp;amp; recursion&lt;/h3&gt;&lt;p&gt;If our function has recursion (or loops which can be expressed as recursion), an
expression can be run multiple times in a single call. How do we let the user
explore all different usages?&lt;/p&gt;&lt;p&gt;As long as our arguments are immutable, storing the arguments to all calls of a
function is generally not significantly expensive. Then the user can selectively
introspect a specific call, or see the value of an expression/return value
across a series of calls.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;factorial 0 = 2 -- Uh oh, this should be 1!
factorial x = x * factorial (x - 1)

-- Seeing the return values across the call stack
-- makes finding the bug easy
&amp;gt;&amp;gt; factorial 3
factorial 3 -- 12
factorial 2 -- 4
factorial 1 -- 2
factorial 0 -- 2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In pathological cases where complete storage is too expensive, we can still:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Explore a single branch of recursion rather than the entire tree, which is
guaranteed to be as cheap as the function call&lt;/li&gt;&lt;li&gt;If the input range of the expression is small, wrap it in an anonymous
function and observe all the times it is called&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Retrofitting existing languages&lt;/h3&gt;&lt;p&gt;We can inefficiently implement this in any functional language with a REPL via
code transformation. Adding wrappers around the tested function and expressions
makes it easy to track their values and arguments.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;factorial x = x * factorial (x - 1)
=&amp;gt; factorial x = memoize
    	(Memo {args=[x], value=x * factorial (x - 1)})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But this requires re-running the entire function on change! A key feature of
REPLs and computational notebooks is running an expensive expression once, and
then re-using it. Can we do this automatically?&lt;/p&gt;&lt;h3&gt;Taking advantage of purely functional code&lt;/h3&gt;&lt;p&gt;If we know parts of our code are purely functional, then yes! Let's look at a
simplified model of function design to see why.&lt;/p&gt;&lt;p&gt;Say a function is a series of assignments, that eventually returns an expression:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;format_matrix matrix = -- some expensive computation

make_csv headers matrix = csv_output
  where
    formatted_matrix = format_matrix matrix
    csv_lines = map (join &amp;quot;,&amp;quot;) (headers : formatted_matrix)
    csv_output = join &amp;quot;\n&amp;quot; csv_lines&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we want to change the file to be tab delimited:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;format_matrix matrix = -- some expensive computation

make_csv headers matrix = csv_output
  where
    formatted_matrix = format_matrix matrix
    csv_lines = map (join &amp;quot;\t&amp;quot;) (headers : formatted_matrix)
    csv_output = join &amp;quot;\n&amp;quot; csv_lines&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We know that &lt;code&gt;format_matrix&lt;/code&gt; has no side effects, so we can skip it and just
re-run the &lt;code&gt;csv_lines&lt;/code&gt; and &lt;code&gt;csv_output&lt;/code&gt; expressions. Or, inversely, we know we
only have to re-run &lt;code&gt;csv_lines&lt;/code&gt; and the things that depend on it.&lt;/p&gt;&lt;p&gt;If we develop a function top-down, we'll generally only have to re-run the last
expression. This is potentially much more efficient than running a test case
from scratch.&lt;/p&gt;&lt;h2&gt;Parting thoughts&lt;/h2&gt;&lt;p&gt;For decades, our solution to fundamental challenges with programming has been
&amp;quot;make a new language&amp;quot;. This is important, but I think we have neglected
innovating how we &lt;em&gt;interface&lt;/em&gt; with code. Going beyond the REPL is just one step
in improving our programming methodologies.&lt;/p&gt;</content></entry><entry><title>Convenience is a bad goal</title><id>https://dominicm.dev/convenience-is-a-bad-goal.html</id><author><name>Dominic Martinez</name><email>dom@dominicm.dev</email></author><updated>2023-10-13T12:07:00Z</updated><link href="https://dominicm.dev/convenience-is-a-bad-goal.html" rel="alternate" /><content type="html">&lt;aside class=&quot;inline info&quot;&gt;&lt;h2&gt;Disclaimer&lt;/h2&gt;&lt;p&gt;Something about this post doesn't feel right to me. Maybe it's how
first-world/privileged it is. It also just seems…too narrow. Convenience is one
of many factors we like, and bad products can manipulate us in other ways.&lt;/p&gt;&lt;p&gt;For that matter, what is convenience? What is it compared to addiction? Where
does convenience transition from rational time-savings to penny-pinching
minutia? How does it play into our energy and will?&lt;/p&gt;&lt;p&gt;I have more thinking and research to do on this topic, but I'm publishing this
post because I think it's still an important part of design to consider.&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;Everyone likes convenience. We like it so much that many of our day-to-day
decisions revolve around it. But just because something is convenient doesn't
mean it's good—or even enjoyable!&lt;/p&gt;&lt;p&gt;Convenience can be a useful tool: when we make &lt;em&gt;good&lt;/em&gt; things convenient (e.g.
public transport), we encourage people to use them. However, modern
society often enables unhealthy forms of convenience.&lt;/p&gt;&lt;h2&gt;Abstracting cost&lt;/h2&gt;&lt;p&gt;Convenience, and particularly technological convenience, is used to hide real
costs. When you order delivery you no longer interact with the restaurant or
delivery driver. When you buy on Amazon you no longer interact with a store and
cashier. Businesses intentionally hide these externalities so they don't factor
into your mental calculus. Abstracting away the costs involved in business and
reducing everything to a monetary exchange robs individuals of agency and
importance.&lt;/p&gt;&lt;h2&gt;Consumerism&lt;/h2&gt;&lt;p&gt;The advent of an economy where people simply continue buying more and more new
things is relatively recent. We used to be able to fix products, or continue to
reuse them for a long time. However, given a design trade-off between
convenience and sustainability we almost universally choose convenience.&lt;/p&gt;&lt;p&gt;Think of every wrapper from the grocery store and every box from an online
purchase. Convenience has increased both the waste of each purchase and how much
we buy.&lt;/p&gt;&lt;h2&gt;Discomfort is important&lt;/h2&gt;&lt;p&gt;Convenience often caters to our avoidant tendencies: we use it to dodge
discomfort, even when that discomfort is normal and healthy.&lt;/p&gt;&lt;p&gt;If you are forced to confront the reality of where your food and products come
from, to interact with someone, or to see what other people are going through,
&lt;em&gt;that is a good thing&lt;/em&gt;. Avoiding discomfort is never worth avoiding the truth
that comes with it.&lt;/p&gt;</content></entry><entry><title>Visual clarity of code</title><id>https://dominicm.dev/visual-clarity-of-code.html</id><author><name>Dominic Martinez</name><email>dom@dominicm.dev</email></author><updated>2022-12-13T12:00:00Z</updated><link href="https://dominicm.dev/visual-clarity-of-code.html" rel="alternate" /><content type="html">&lt;aside class=&quot;inline info&quot;&gt;&lt;h2&gt;Disclaimer&lt;/h2&gt;&lt;p&gt;This post is old and in need of review. Yet another box on my to-do list that
I will &lt;em&gt;definitely&lt;/em&gt; get to.&lt;/p&gt;&lt;/aside&gt;&lt;p&gt;When discussing how easy a programming language is to read or write, we
primarily think about functionality; what abstractions does the language provide
to make programming easier? What's not often mentioned explicitly—even if we
recognize it instinctually—is the visual clarity of the language.&lt;/p&gt;&lt;p&gt;Take the following snippets:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;syntax-special&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;syntax-function&quot;&gt;mult_by_2&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;:
  &lt;span class=&quot;syntax-special&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;syntax-constant&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;syntax-symbol&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;syntax-special&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;syntax-function&quot;&gt;mult_by_2&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;:
  &lt;span class=&quot;syntax-special&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;syntax-constant&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;syntax-special&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-special&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You probably found the second snippet more readable. We're operating at the same
level of abstraction: these two snippets map one-to-one. However, the list
comprehension syntax makes it visually clear that we're mapping elements of a
list, and presents the information in a more natural reading order. We've gained
type information and readability through a bit of syntax sugar.&lt;/p&gt;&lt;p&gt;Let's look at a more extreme example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;integrate&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;/&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;exp&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;sqrt&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msubsup&gt;&lt;mo&gt;∫&lt;/mo&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/msubsup&gt;&lt;mfrac&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;/&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;msqrt&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;/mrow&gt;&lt;/msqrt&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\int_a^b \frac{e^{-x^2/2}}{\sqrt{2\pi}}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:2.5939em;vertical-align:-0.93em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mop&quot;&gt;&lt;span class=&quot;mop op-symbol large-op&quot; style=&quot;margin-right:0.44445em;position:relative;top:-0.0011em;&quot;&gt;∫&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.599em;&quot;&gt;&lt;span style=&quot;top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.8129em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9119em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.1667em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.6639em;&quot;&gt;&lt;span style=&quot;top:-2.2028em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord sqrt&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9072em;&quot;&gt;&lt;span class=&quot;svg-align&quot; style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot; style=&quot;padding-left:0.833em;&quot;&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;π&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-2.8672em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hide-tail&quot; style=&quot;min-width:0.853em;height:1.08em;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;400em&quot; height=&quot;1.08em&quot; viewBox=&quot;0 0 400000 1080&quot; preserveAspectRatio=&quot;xMinYMin slice&quot;&gt;&lt;path d=&quot;M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z&quot;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.1328em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.677em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9869em;&quot;&gt;&lt;span style=&quot;top:-3.063em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.8913em;&quot;&gt;&lt;span style=&quot;top:-2.931em;margin-right:0.0714em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.5em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size3 size1 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;/2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.93em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/p&gt;&lt;p&gt;Fundamentally, these two snippets represent the same operation at the same
level of abstraction. Yet the mathematical notation visually communicates that:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We're doing math&lt;/li&gt;&lt;li&gt;We're performing an integral from a to b&lt;/li&gt;&lt;li&gt;We have a fraction with an exponential term over a constant&lt;/li&gt;&lt;li&gt;This is an integral over the normal distribution&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Teasing this information out of the Lisp program takes a lot more effort.&lt;/p&gt;&lt;p&gt;Except, well, you actually might not have understood the mathematical notation
at all! Sure, you can technically grasp all that information quicker from the
mathematical notation, but you could also be unfamiliar with something like
integration and be left without even a function name to look up.&lt;/p&gt;&lt;h2&gt;Climbing the curve&lt;/h2&gt;&lt;p&gt;The Lisp snippet has a much lower barrier to entry. As long as you know Lisp
syntax (which is famously simple enough to fit on a business card), you can see
what function is applied to what variables/numbers. And if you're lost as to
what &lt;code&gt;integrate&lt;/code&gt; or &lt;code&gt;exp&lt;/code&gt; do, you can look it up pretty easily.&lt;/p&gt;&lt;p&gt;On the other hand, getting a complete picture of the equation in Lisp is
difficult. If you can read the mathematical equation, you probably find that
syntax much easier to comprehend.&lt;/p&gt;&lt;p&gt;Every piece of syntax represents a chance to add a visually distinct operation,
but also increases the learning curve—especially for reading code.&lt;/p&gt;&lt;p&gt;This is (in my opinion) one reason why many find Lisp and APL difficult to
use despite them being such powerful languages. In Lisp, everything has exactly
one visual form, rendering visual processing of the code very difficult. APL's
symbols make it easier to scan for operations, but because it is almost entirely
non-textual and doesn't use other visual demarcations like whitespace, a
massive amount of memorization is necessary.&lt;/p&gt;&lt;figure&gt;&lt;pre&gt;&lt;code&gt;life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}&lt;/code&gt;&lt;/pre&gt;&lt;figcaption&gt;&lt;p&gt;An &lt;a href=&quot;https://aplwiki.com/wiki/Conway%27s_Game_of_Life&quot;&gt;APL implementation&lt;/a&gt;
of Conway's Game of Life. Impressive stuff—for the people who understand it.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Figuring out how to create programs that are both visual and readable is
important. Math and engineering rely heavily on symbols and visualizations for a
reason; the human brain is an incredibly efficient visual processor, which we neglect
when we adopt a single syntax for every problem domain. In general-purpose
languages, all code devolves into a series of functions and control statements,
regardless of the code's meaning.&lt;/p&gt;&lt;p&gt;Imagine if there was an easily recognizable syntax for accessing a database,
or logging a block of code. Every hint we give to our pattern recognition system
makes understanding code easier, and we should take advantage of it.&lt;/p&gt;&lt;p&gt;So let's take a look at the practical challenges of visual programming
representation and how we might approach them.&lt;/p&gt;&lt;h2&gt;Dealing with the domain&lt;/h2&gt;&lt;p&gt;It's very difficult—perhaps impossible—to have the necessary visual patterns in a
general-purpose language out of the box. Common patterns vary between domains,
specific applications, and even sub-applications. Users need to be able to
create their own syntax to express their architecture.&lt;/p&gt;&lt;p&gt;Lisp does the functionality part of this pretty well, but it misses out on
visual clarity due to its limited syntax. Other languages do some form of this,
but I've not found a language that's truly flexible in terms of program syntax
yet.&lt;/p&gt;&lt;p&gt;But there's good reason even macro-focused languages don't venture here. Even
ignoring technical challenges like parsing, how would we make such a language
readable to people outside the domain? Due to the wide applicability of
programming, programmers shift between domains more often than in many other
knowledge fields; if it takes a week to learn the application syntax, we can
basically kiss open-source contributions goodbye. We need another solution.&lt;/p&gt;&lt;h2&gt;Multiple representations&lt;/h2&gt;&lt;p&gt;Putting a layer of indirection between the canonical program representation and
what the user sees is not unheard of; it's what every
&lt;a href=&quot;https://en.wikipedia.org/wiki/WYSIWYG&quot;&gt;WYSIWYG&lt;/a&gt; editor does. In programming,
projects like &lt;a href=&quot;https://enso.org/&quot;&gt;Enso&lt;/a&gt;, &lt;a href=&quot;https://squeak.org/&quot;&gt;Smalltalk&lt;/a&gt;, or
even syntax highlighting act as middlemen between the literal program file and
what shows up on the screen. Using indirection, we can modify what people see to
align with their experience level. A symbolic language like APL could show
textual versions of its operations, or a textual data science language could
show a boxes-and-lines-style visual representation.&lt;/p&gt;&lt;p&gt;This is similar in spirit to the gradual programming model of
&lt;a href=&quot;https://hedy-beta.herokuapp.com/&quot;&gt;Hedy&lt;/a&gt;. The needs of beginners and experts are
different, and switching program representations should accommodate that.&lt;/p&gt;&lt;h2&gt;Language support&lt;/h2&gt;&lt;p&gt;The previous section hints at how this could actually work. Even with custom
syntax, there's a canonical AST that can be used by tooling. There's even prior
work here: &lt;a href=&quot;https://tree-sitter.github.io/tree-sitter/&quot;&gt;Tree-sitter&lt;/a&gt; provides
language-independent syntax higlighting, and
&lt;a href=&quot;https://github.com/ethan-leba/tree-edit&quot;&gt;Tree-edit&lt;/a&gt; provides structural editing
on top of that AST.&lt;/p&gt;&lt;h2&gt;So what?&lt;/h2&gt;&lt;p&gt;OK, say you're not convinced that we need better visualizations of code.
What does all this work get us?&lt;/p&gt;&lt;p&gt;As is often the case when indirection is introduced, a whole lot.&lt;/p&gt;&lt;p&gt;Bidirectional representations for editing source code are fairly limited, since
you have to maintain complete information over the transformation. But what if
you allowed one-way transformations? You probably don't care what AST your
programming language uses under the hood, so what if they just used the same
one?&lt;/p&gt;&lt;p&gt;Every time we start a new language, we start at almost a blank slate. Debugging,
editing, profiling, introspection, toolkits, package management; every language
rebuilds these ideas all over again. Hell, nowadays these tend to be the killer
features of new languages. We should be able to focus on the language syntax and
features—the &lt;em&gt;representation&lt;/em&gt; of the language—separately from the tooling.&lt;/p&gt;&lt;p&gt;Having this separation makes it much easier to develop small,
easy-to-reason-about DSLs instead of hammering everything into general-purpose
languages.&lt;/p&gt;&lt;p&gt;We already have this functionality in highly programmable languages like Lisps.
It's how we can get powerful features like pattern matching without compiler
support. All we're missing is the ability to visualize our new constructs in
readable ways.&lt;/p&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;Hopefully this post gave you some new insights. This space is massive, and
there's probably a lot of research material out there I'm not aware of. I've
included some further reading below; if there's something cool I'm missing let
me know and I'll add it in!&lt;/p&gt;&lt;h2&gt;Further reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://jackrusher.com/strange-loop-2022/&quot;&gt;Stop writing dead programs&lt;/a&gt;: A wonderful
talk by Jack Rusher that goes into syntax and visual program representation,
among other topics.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ubaX1Smg6pY&quot;&gt;Is it really &amp;quot;Complex&amp;quot;? Or did we just make it
&amp;quot;Complicated&amp;quot;?&lt;/a&gt;: A famous talk by
Alan Kay about using smarter, smaller languages.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://enso.org/&quot;&gt;Enso&lt;/a&gt;: A data science language with a
boxes-and-lines representation.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.subtext-lang.org/&quot;&gt;Subtext&lt;/a&gt;: A &amp;quot;programming by example&amp;quot;
language with a custom editing UI.&lt;/li&gt;&lt;/ul&gt;</content></entry></feed>