tag:blogger.com,1999:blog-17779909838478118062024-03-16T16:29:29.668-07:00Haskell for allGabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.comBlogger181125tag:blogger.com,1999:blog-1777990983847811806.post-53123450686160315552024-02-29T04:47:00.000-08:002024-02-29T04:47:45.811-08:00The siren song of domain-specific languages<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:creator" content="@GabriellaG439" />
<meta name="twitter:title" content="The siren song of domain-specific languages" />
<meta name="twitter:description" content="The pitfalls software engineers encounter when creating domain-specific languages for less technical users" />
<title>The siren song of domain-specific languages</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>I’ve seen a lot of engineering teams mistakenly believe that they can
author domain-specific languages for less technical users on a budget.
In particular they seem to believe that if they create this
domain-specific language then the less technical users will be able to
thoughtlessly churn out a bunch of code in that language and there won’t
be any problem and they can then move onto the next project. This rarely
works out in the way that people hope it will.</p>
<p>In the <em>best case scenario</em>, your less technical users will
churn out a large amount of code using your domain-specific language
(which is exactly the outcome you hoped for!) and that corpus of code
will push the boundaries of what your language is capable of (like
performance, compilation speed, features, or supporting integrations).
The larger your userbase the greater the demand will be to improve your
language in a myriad of ways.</p>
<p>In the worst case scenario your users will find increasingly inane
ways to do things wrong with your language despite your best efforts and
you will be expected to clean up their mess because you sold the project
on the premise of “our users are not going to have to think”.</p>
<p>… and in either case this process will never end; the project will
never be in a “done state” and require permanent staffing. Hell, even if
you staff an entire team to support this language it’s still often a
struggle to keep up with the needs of less technical users.</p>
<p>This tradeoff can still tempt businesses because it’s appealing to
replace skilled labor with unskilled labor. The reasoning goes that a
small investment of more skilled labor (the authors of the
domain-specific language) can enable a larger pool of less skilled labor
(the less technical users) to do most of the work. However, what you
will often find in practice is that this larger group of less technical
users is frequently blocked without continuous assistance from the
engineers who created the language.</p>
<p>So in practice you’re not actually replacing skilled labor with
unskilled labor. Rather, you’re merely “laundering” skilled labor as
unskilled labor and creating more work for your engineers to make them
seem more replaceable than they actually are.</p>
<p>I do think there are situations where domain-specific languages make
sense, but typically not on the scale of a software engineering
organization or even a small product. I personally think this sort of
division of labor tends to only work on the scale of an open source
ecosystem where you get a large enough economy of scale.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com3tag:blogger.com,1999:blog-1777990983847811806.post-40275601038460300652024-02-22T05:42:00.000-08:002024-02-22T08:04:36.645-08:00Unification-free ("keyword") type checking<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:author" content="@GabriellaG439" />
<meta name="twitter:title" content="Unification-free ("keyword") type checking" />
<meta name="twitter:description" content="A type checking proposal that doesn't employ unification variables." />
<title>Unification-free ("keyword") type checking</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>From my perspective, one of the biggest open problems in implementing
programming languages is how to add a type system to the language
without significantly complicating the implementation.</p>
<p>For example, in my tutorial <a
href="https://github.com/Gabriella439/grace">Fall-from-Grace</a>
implementation the type checker logic accounts for over half of the
code. In the following lines of code report I’ve highlighted the modules
responsible for type-checking with a <code>‡</code>:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cloc <span class="at">--by-file</span> src/Grace/<span class="pp">*</span>.hs </span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">--------------------------------------------------------------------------------</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">File</span> blank comment code</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">--------------------------------------------------------------------------------</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Infer.hs</span> ‡ 499 334 1696</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Type.hs</span> ‡ 96 91 633</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Syntax.hs</span> 61 163 543</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Parser.hs</span> 166 15 477</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Lexer.hs</span> 69 25 412</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Normalize.hs</span> 47 48 409</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Context.hs</span> ‡ 72 165 249</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Import.hs</span> 38 5 161</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/REPL.hs</span> 56 4 148</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Interpret.hs</span> 30 28 114</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Pretty.hs</span> 25 25 108</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Monotype.hs</span> ‡ 11 48 61</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Location.hs</span> 16 15 60</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/TH.hs</span> 23 32 53</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Value.hs</span> 12 53 53</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Input.hs</span> 10 8 43</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Compat.hs</span> 9 2 32</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Existential.hs</span> ‡ 12 23 25</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="ex">src/Grace/Domain.hs</span> ‡ 4 7 20</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a><span class="ex">--------------------------------------------------------------------------------</span></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a><span class="ex">SUM:</span> 1256 1091 5297</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a><span class="ex">--------------------------------------------------------------------------------</span></span></code></pre></div>
<p>That’s 2684 lines of code (≈51%) just for type-checking (and believe
me: I tried very hard to simplify the type-checking code).</p>
<p>This is the reason why programming language implementers will be
pretty keen to just not implement a type-checker for their language, and
that’s how we end up with a proliferation of untyped programming
languages (e.g. Godot or Nix), or ones that end up with a type system
bolted on long after the fact (e.g. TypeScript or Python). You can see
why someone would be pretty tempted to skip implementing a type system
for their language (especially given that it’s an optional language
feature) if it’s going to balloon the size of their codebase.</p>
<p>So I’m extremely keen on implementing a “lean” type checker that has
a high power-to-weight ratio. I also believe that a compact type checker
is an important foundational step for functional programming to “go
viral” and displace imperative programming. This post outlines one
approach to this problem that I’ve been experimenting with<a href="#fn1"
class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a>.</p>
<h4 id="unification">Unification</h4>
<p>The thing that bloats the size of most type-checking implementations
is the need to track unification variables. These variables are
placeholders for storing as-yet-unknown information about something’s
type.</p>
<p>For example, when a functional programming language infers the type
of something like this Grace expression:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>(λx → x) true</span></code></pre></div>
<p>… the way it typically works is that it will infer the type of the
function (<code>λx → x</code>) which will be:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>λx → x <span class="op">:</span> α → α</span></code></pre></div>
<p>… where <code>α</code> is a unification variable (an unsolved type).
So you can read the above type annotation as saying “the type of
<code>λx → x</code> is a function from some unknown input type
(<code>α</code>) to the same output type (<code>α</code>).</p>
<p>Then the type checker will infer the type of the function’s input
argument (<code>true</code>) which will be:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>true <span class="op">:</span> <span class="dt">Bool</span></span></code></pre></div>
<p>… and finally the type checker will combine those two pieces of
information and reason about the final type like this:</p>
<ul>
<li>the input to the function (<code>true</code>) is a
<code>Bool</code></li>
<li>therefore the function’s input type (<code>α</code>) must also be
<code>Bool</code></li>
<li>therefore the function’s output type (<code>α</code>) must also be
<code>Bool</code></li>
<li>therefore the entire expression’s type is <code>Bool</code></li>
</ul>
<p>… which gives the following conclusion of type inference:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>(λx → x) true <span class="op">:</span> <span class="dt">Bool</span></span></code></pre></div>
<p>However, managing unification variables like <code>α</code> is a lot
trickier than it sounds. There are multiple unification
algorithms/frameworks in the wild but the problem with all of them is
that you have to essentially implement a bespoke logic programming
language (with all of the complexity that entails). Like, geez, I’m
already implementing a programming language and I don’t want to have to
implement a logic programming language on top of that just to power my
type-checker.</p>
<p>So there are a couple of ways I’ve been brainstorming how to address
this problem and one idea I had was: what if we could get rid of
unification variables altogether?</p>
<h4 id="deleting-unification">Deleting unification</h4>
<p>Alright, so this is the part of the post that requires some
familiarity/experience with implementing a type-checker. If you’re
somebody new to programming language theory then you can still keep
reading but this is where I have to assume some prior knowledge
otherwise this post will get way too long.</p>
<p>The basic idea is that you start from <a
href="https://www.cl.cam.ac.uk/~nk480/bidir.pdf">the “Complete and Easy”
bidirectional type checking algorithm</a> which is a type checking
algorithm that does use unification variables<a href="#fn2"
class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> but
is simpler than most type checking algorithms. The type checking rules
look like this (you can just gloss over them):</p>
<p><img
src="https://staging.cohostcdn.org/attachment/54a5a565-0f21-4d1f-8156-af0f613bee6c/Bildschirmfoto%202024-02-19%20um%2010.14.01%E2%80%AFAM.png" /></p>
<p><img
src="https://staging.cohostcdn.org/attachment/7513477e-79d8-4677-a24c-10d788bf57c0/Bildschirmfoto%202024-02-19%20um%2010.14.16%E2%80%AFAM.png" /></p>
<p>Now, delete all the rules involving unification variables. Yes, all
of them. That means that all of the type-checking judgments from Figures
9 and 10 are gone and also quite a few rules from Figure 11 disappear,
too.</p>
<p>Surprisingly, you can still type check a lot of code with what’s
left, but you lose two important type inference features if you do
this:</p>
<ul>
<li><p>you can no longer infer the types of lambda arguments</p></li>
<li><p>you can no longer automatically instantiate polymorphic
code</p></li>
</ul>
<p>… and I’ll dig into those two issues in more detail.</p>
<h4 id="inferring-lambda-argument-types">Inferring lambda argument
types</h4>
<p>You lose the ability to infer the type of a function like this one
when you drop support for unification variables:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>λx → x <span class="op">==</span> <span class="dt">False</span></span></code></pre></div>
<p>Normally, a type checker that supports unification can infer that the
above function has type <code>Bool → Bool</code>, but (in general) a
type checker can no longer infer that when you drop unification
variables from the implementation.</p>
<p>This loss is not <em>too</em> bad (in fact, it’s a pretty common
trade-off proposed in the bidirectional type checking literature)
because you can make up for it in a few ways (all of which are easy and
efficient to implement in a type checker):</p>
<ul>
<li><p>You can allow the input type to be inferred if the lambda is
given an explicit type annotation, like this:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>λx → x <span class="op">==</span> <span class="dt">False</span> <span class="op">:</span> <span class="dt">Bool</span> → <span class="dt">Bool</span></span></code></pre></div>
<p>More generally, you can allow the input type to be inferred if the
lambda is checked against an expected type (and a type annotation is one
case, but not the only case, where a lambda is checked against an
expected type).</p>
<p>We’re going to lean on this pretty heavily because it’s pretty
reasonable to ask users to provide type annotations for function
definitions and also because there are many situations where we can
infer the expected type of a lambda expression from its immediate
context.</p></li>
<li><p>You can allow the user to explicitly supply the type of the
argument</p>
<p>… like this:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>λ(x <span class="op">:</span> <span class="dt">Bool</span>) → x <span class="op">==</span> <span class="dt">False</span></span></code></pre></div>
<p>This is how <a href="https://dhall-lang.org/">Dhall</a> works,
although it’s not as ergonomic.</p></li>
<li><p>You can allow the input type to be inferred if the lambda is
applied to an argument</p>
<p>This is not that interesting, but I’m mentioning it for completeness.
The reason it’s not interesting is because you won’t often see
expressions of the form <code>(λx → e) y</code> in the wild, because
they can more idiomatically be rewritten as
<code>let x = y in e</code>.</p></li>
</ul>
<h4 id="instantiating-polymorphic-code">Instantiating polymorphic
code</h4>
<p>The bigger issue with dropping support for unification variables is:
all user-defined polymorphic functions now require <em>explicit type
abstraction</em> and <em>explicit type application</em>, which is a
major regression in the type system’s user experience.</p>
<p>For example, in a language with unification variables you can write
the polymorphic identity function as:</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>λx → x</span></code></pre></div>
<p>… and use it like this<a href="#fn3" class="footnote-ref" id="fnref3"
role="doc-noteref"><sup>3</sup></a>:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="fu">id</span> <span class="ot">=</span> λx → x</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> (<span class="fu">id</span> true, <span class="fu">id</span> <span class="dv">1</span>)</span></code></pre></div>
<p>… but when you drop support for unification variables then you have
to do something like this:</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="fu">id</span> <span class="ot">=</span> λ(a <span class="op">:</span> <span class="dt">Type</span>) → λ(x <span class="op">:</span> a) → x</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> (<span class="fu">id</span> <span class="dt">Bool</span> true, <span class="fu">id</span> <span class="dt">Natural</span> <span class="dv">1</span>)</span></code></pre></div>
<p>Most programmers do <strong>NOT</strong> want to program in a
language where they have to explicitly manipulate type variables in this
way. In particular, they really hate explicit type application. For
example, nobody wants to write:</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="fu">map</span> { x <span class="op">:</span> <span class="dt">Bool</span>, … large record … } <span class="dt">Bool</span> (λr → r<span class="op">.</span>x) rs</span></code></pre></div>
<p>So we need to figure out some way to work around this limitation.</p>
<h4 id="the-trick">The trick</h4>
<p>However, there is a solution that I believe gives a high
power-to-weight ratio, which I will refer to as “keyword” type
checking:</p>
<ul>
<li><p>add a bunch of built-in functions</p>
<p>Specifically, add enough built-in functions to cover most use cases
where users would need a polymorphic function.</p></li>
<li><p>add special type-checking rules for those built-in functions when
they’re fully saturated with all of their arguments</p>
<p>These special-cased type-checking rules would not require unification
variables.</p></li>
<li><p>still require explicit type abstraction when these built-in
functions are not fully saturated</p>
<p>Alternatively, you can require that built-in polymorphic functions
are fully saturated with their arguments and make it a parsing error if
they’re not.</p></li>
<li><p>still require explicit type abstraction and explicit type
application for all user-defined (i.e. non-builtin) polymorphic
functions</p></li>
<li><p>optionally, turn these built-in functions into keywords or
language constructs</p></li>
</ul>
<p>I’ll give a concrete example: the <code>map</code> function for
lists. In many functional programming languages this <code>map</code>
function is not a built-in function; rather it’s defined within the host
language as a function of the following type:</p>
<div class="sourceCode" id="cb13"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="fu">map</span> <span class="op">:</span> ∀(a b <span class="op">:</span> <span class="dt">Type</span>) → (a → b) → <span class="dt">List</span> a → <span class="dt">List</span> b</span></code></pre></div>
<p>What I’m proposing is that the <code>map</code> function would now
become a built-in function within the language and you would now apply a
special type-checking rule when the <code>map</code> function is fully
saturated:</p>
<pre><code>Γ ⊢ xs ⇒ List a Γ ⊢ f ⇐ a → b
───────────────────────────────
Γ ⊢ map f xs ⇐ List b</code></pre>
<p>In other words, we’re essentially treating the <code>map</code>
built-in function like a “keyword” in our language (when it’s fully
saturated). Just like a keyword, it’s a built-in language feature that
has special type-checking rules. Hell, you could even make it an actual
keyword or language construct (e.g. a list comprehension) instead of a
function call.</p>
<p>I would even argue that you should make each of these special-cased
builtin-functions a keyword or a language construct instead of a
function call (which is why I call this “keyword type checking” in the
first place). When viewed through this lens the restrictions that these
polymorphic built-in functions (A) are saturated with their arguments
and (B) have a special type checking judgment are no different than the
restrictions for ordinary keywords or language constructs (which also
must be saturated with their arguments and also require special type
checking judgments).</p>
<p>To make an analogy, in many functional programming languages the
<code>if</code>/<code>then</code>/<code>else</code> construct has this
same “keyword” status. You typically don’t implement it as a user-space
function of this type:</p>
<div class="sourceCode" id="cb15"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a>ifThenElse <span class="op">:</span> ∀(a <span class="op">:</span> <span class="dt">Type</span>) → <span class="dt">Bool</span> → a → a → a</span></code></pre></div>
<p>Rather, you define <code>if</code> as a language construct and you
also add a special type-checking rule for <code>if</code>:</p>
<pre><code>Γ ⊢ b ⇐ Bool Γ ⊢ x ⇒ a Γ ⊢ y ⇐ a
────────────────────────────────────
Γ ⊢ if b then x else y ⇒ a</code></pre>
<p>… and what I’m proposing is essentially greatly exploding the number
of “keywords” in the implementation of the language by turning a whole
bunch of commonly-used polymorphic functions into built-in functions (or
keywords, or language constructs) that are given special type-checking
treatment.</p>
<p>For example, suppose the user were to create a polymorphic function
like this one:</p>
<div class="sourceCode" id="cb17"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> twice <span class="ot">=</span> λ(a <span class="op">:</span> <span class="dt">Type</span>) → λ(x <span class="op">:</span> a) → [ x, x ]</span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> twice (<span class="dt">List</span> <span class="dt">Bool</span>) (twice <span class="dt">Bool</span> true)</span></code></pre></div>
<p>That’s not very ergonomic to define and use, but we also can’t
reasonably expect our programming language to provide a
<code>twice</code> built-in function. However, our language could
provide a generally useful <code>replicate</code> builtin function (like
<a
href="https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-List.html#v:replicate">Haskell’s
<code>replicate</code> function</a>):</p>
<div class="sourceCode" id="cb18"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="fu">replicate</span> <span class="op">:</span> ∀(a <span class="op">:</span> <span class="dt">Type</span>) → <span class="dt">Natural</span> → a → <span class="dt">List</span> a</span></code></pre></div>
<p>… with the following type-checking judgment:</p>
<pre><code>Γ ⊢ n ⇐ Natural Γ ⊢ x ⇒ a
───────────────────────────
Γ ⊢ replicate n x ⇒ List a</code></pre>
<p>… and then you would tell the user to use <code>replicate</code>
directly instead of defining their own <code>twice</code> function:</p>
<div class="sourceCode" id="cb20"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="fu">replicate</span> <span class="dv">2</span> (<span class="fu">replicate</span> <span class="dv">2</span> true)</span></code></pre></div>
<p>… and if the user were to ask you “How do I define a
<code>twice</code> synonym for <code>replicate 2</code>” you would just
tell them “Don’t do that. Use <code>replicate 2</code> directly.”</p>
<h4 id="conclusion">Conclusion</h4>
<p>This approach has the major upside that it’s much easier to implement
a large number of keywords than it is to implement a unification
algorithm, but there are <em>other</em> benefits to doing this, too!</p>
<ul>
<li><p>It discourages complexity and fragmentation in user-space
code</p>
<p>Built-in polymorphic functions have an ergonomic advantage over
user-defined polymorphic functions because under this framework type
inference works better for built-in functions. This creates an ergonomic
incentive to stick to the “standard library” of built-in polymorphic
functions, which in turn promotes an opinionated coding style across all
code written in that language.</p>
<p>You might notice that this approach is somewhat similar in spirit to
how Go handles polymorphism which is to say: it doesn’t handle
user-defined polymorphic code well. For example, Go provides a few
built-in language features that support polymorphism (e.g. the
<code>map</code> data structure and for loops) but if users ask for any
sort of user-defined polymorphism then the maintainers tell them they’re
wrong for wanting that. The main difference here is that (unlike Go) we
do actually support user-defined polymorphism; it’s not forbidden, but
it is less ergonomic than sticking to the built-in utilities that
support polymorphism..</p></li>
<li><p>It improves error messages</p>
<p>When you special-case the type-checking logic you can also
special-case the error messages, too! With general-purpose unification
the error message can often be a bit divorced from the user’s intent,
but with “keyword type checking” the error message is not only more
local to the problem but it can also suggest highly-specific tips or
fixes appropriate for that built-in function (or keyword or language
construct).</p></li>
<li><p>It can in some cases more closely match the expectations of
imperative programmers</p>
<p>What I mean is: most programmers coming from an imperative and typed
background are used to languages where (most of the time) polymorphism
is “supported” via built-in language constructs and keywords and
user-defined polymorphism might be supported but considered “fancy”.
Leaning on polymorphism via keywords and language constructs would
actually make them more comfortable using polymorphism instead of trying
to teach them how to produce and consume user-defined polymorphic
functions.</p>
<p>For example, in a lot of imperative languages the idiomatic solution
for how to do anything with a list is “use a for loop” where you can
think of a for loop as a built-in keyword that supports polymorphic
code. The functional programming equivalent of “just use a for loop”
would be something like “just use a list comprehension” (where a list
comprehension is a “keyword” that supports polymorphic code that we can
give special type checking treatment).</p></li>
</ul>
<p>That said, this approach is still more brittle than unification and
will require more type annotations in general. The goal here isn’t to
completely recover the full power of unification but rather to get
something that’s not too bad but <em>significantly</em> easier to
implement.</p>
<p>I think this “keyword type checking” can potentially occupy a “low
tech” point in the type checking design space for functional programming
languages that need to have efficient and compact implementations
(e.g. for ease of embedding). Also, this can potentially provide a
stop-gap solution for novice language implementers that want
<em>some</em> sort of a type system but they’re not willing to commit to
implementing a unification-based type system.</p>
<p>There’s also variation on this idea which Verity Scheel has been
exploring, which is to provide userland support for defining new
functions with special type-checking rules and there’s a post from her
outlining how to do that:</p>
<p><a href="https://cofree.coffee/~verity/implicit_arguments.html">User
Operators with Implicits & Overloads</a></p>
<section class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1" role="doc-endnote"><p>The other approach is to create
essentially an “ABNF for type checkers” that would let you write
type-checking judgments in a standard format that could generate the
corresponding type-checking code in multiple languages. That’s still a
work-in-progress, though.<a href="#fnref1" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn2" role="doc-endnote"><p>I believe some people might take
issue with calling these unification variables because they consider
bidirectional type checking as a distinct framework from unification.
Moreover, in the original bidirectional type checking paper they’re
called “unsolved” variables rather than unification variables. However,
I feel that for the purpose of this post it’s still morally correct to
refer to these unsolved variables as unification variables since their
usage and complexity tradeoffs are essentially identical to unification
variables in traditional unification algorithms.<a href="#fnref2"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3" role="doc-endnote"><p>… assuming <code>let</code>
expressions are generalized.<a href="#fnref3" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-50183440684297653732023-10-04T11:50:00.001-07:002023-10-04T11:50:09.870-07:00A GHC plugin for OpenTelemetry build metrics<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:creator" content="@GabriellaG439" />
<meta name="twitter:title" content="A GHC plugin for OpenTelemetry build metrics" />
<meta name="twitter:description" content="An announcement post for the opentelemetry-plugin Haskell package" />
<title>A GHC plugin for OpenTelemetry build metrics</title>
<style>
html {
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 12px;
}
h1 {
font-size: 1.8em;
}
}
@media print {
html {
background-color: white;
}
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
svg {
height; auto;
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
font-size: 85%;
margin: 0;
hyphens: manual;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This post is about a new <a
href="https://opentelemetry.io/">OpenTelemetry</a> plugin for GHC that
I’ve been building for <a
href="https://github.com/MercuryTechnologies">work</a> that we’re open
sourcing because I think it might be broadly useful to others. If all
you want to do is use the plugin then you can find it <a
href="https://hackage.haskell.org/package/opentelemetry-plugin">on
Hackage</a>, which includes more detailed usage instructions. This post
will focus more on the motivation and background behind the plugin’s
development.</p>
<h4 id="motivation">Motivation</h4>
<p>The context behind this work was that we use <a
href="https://www.honeycomb.io/">Honeycomb</a> at work for collecting
metrics related to production and our team<a href="#fn1"
class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> has
begun to apply those same metrics to our builds. In particular, we
wanted to collect detailed (module-level) build metrics so that we could
begin to hunt down and fix expensive modules within our codebase. For
context: our codebase currently has almost 7000 modules, so these
expensive modules can easily fly under the radar.</p>
<p>When we enable the plugin and export the results to Honeycomb we can
begin to see which modules are the most expensive to build:</p>
<figure>
<img
src="https://user-images.githubusercontent.com/1313787/272380453-2898e7d2-5372-4bfb-85f1-3773b2ba78b1.png"
alt="Sample module build times" />
<figcaption aria-hidden="true">Sample module build times</figcaption>
</figure>
<p>… and none of the modules are individually very expensive to build
(the worst offender is only about 5 seconds), so they’d easily get lost
within a sea of thousands of other modules.</p>
<p>However, these sorts of insights have already proven useful. For
example:</p>
<ul>
<li><p>one expensive modules was completely unused in our codebase</p>
<p>The above list brought it to our attention so that we could delete
it.</p></li>
<li><p>other expensive modules were representative examples of larger
issues to fix</p>
<p>For example, one expensive module consisted of 2000 invocations of an
internal function which is expensive to type-check and fixing this
function will improve compile speeds across our codebase and not just
that module.</p></li>
<li><p>other expensive modules are indicative of architectural
anti-patterns</p>
<p>Frequently “horizontally-organized” modules top the chart, and I view
them as anti-patterns for a few reasons (see: my post on <a
href="https://www.haskellforall.com/2021/05/module-organization-guidelines-for.html">Module
organization guidelines</a>). These modules are not expensive <em>per
se</em> (the code inside them has to be compiled <em>somewhere</em>),
but they tend to be build chokepoints because they have a large number
of dependencies and reverse dependencies. Highlighting expensive modules
has a tendency to highlight these sorts of build chokepoints as a side
bonus.</p></li>
</ul>
<p>In principle you can also browse a given build’s trace interactively,
like this:</p>
<p><img
src="https://user-images.githubusercontent.com/1313787/272098736-1d1e4e7b-4122-45be-8f7b-e74f2ccddab0.png" /></p>
<p>However, for our codebase Honeycomb chokes on our giant build traces
and we can only produce visualizations like the above image if we filter
down the spans to a randomly sampled subset of modules. Honeycomb
doesn’t do a good job of handling traces with a few thousand spans or
more.</p>
<h4 id="workarounds">Workarounds</h4>
<p>This plugin was surprisingly difficult for me to implement because
GHC’s <code>Plugin</code> interface is so constrained.</p>
<p>For example, the <code>hs-opentelemetry-sdk</code> package asks you
to finalize any <code>TracerProvider</code> that you acquire, but
there’s no good way (that I know of<a href="#fn2" class="footnote-ref"
id="fnref2" role="doc-noteref"><sup>2</sup></a>) to run finalization
logic at the end of a <code>ghc</code> build using the
<code>Plugin</code> interface. The purpose of this finalization logic is
to flush metrics that haven’t yet been exported.</p>
<p>So what I did was to hack around this by detecting all modules that
are root modules of the build graph and flushing metrics after each of
those root modules is built (since one of them will be the last module
built). I tried a bunch of other alternative approaches (like installing
a <a
href="https://hackage.haskell.org/package/ghc-9.6.3/docs/GHC-Driver-Pipeline-Phases.html#t:PhaseHook">phase
hook</a>), but this was the only approach I was able to get to work.</p>
<p>And the OpenTelemetry plugin is full of workarounds like this. We
have vetted internally that the plugin works for normal builds,
<code>ghcid</code> and <code>haskell-language-server</code>, but
generally I expect there to be some trailing bugs that we’ll have to fix
as more people use it due to these various unsafe implementation
details.</p>
<p>In fact, one limitation of the plugin is that the top-level span has
a duration of 0 (instead of reporting the duration of the build). This
is related to the same issue of the <code>Plugin</code> interface
apparently not having a good way to run code exactly once after the
build completes (even using hacks). If somebody knows of a way to do
this that I missed I’d definitely welcome the tip!</p>
<h4 id="conclusion">Conclusion</h4>
<p>What we do know from internal usage is that:</p>
<ul>
<li><p>the plugin definitely scales to very large codebases (thousands
of modules)</p>
<p>… although honeycomb doesn’t scale to thousands of spans, but that’s
not our fault.</p></li>
<li><p>the plugin’s overhead is negligible (so it’s safe to always
enable)</p></li>
<li><p>the plugin works with <code>cabal</code> commands,
<code>ghcid</code>, and <code>haskell-language-server</code></p></li>
</ul>
<p>So it should be fine for most use cases, but please report any issues
that you run into.</p>
<aside id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>the backend developer user experience team<a
href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>I’d be really happy to be proven wrong, so if someone
knows the right way to do this please <a
href="https://github.com/MercuryTechnologies/opentelemetry-plugin/issues">open
an issue</a> explaining this (and I can fix it myself) or submit a pull
request<a href="#fnref2" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</aside>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-32305718421600759162023-10-02T10:36:00.002-07:002023-10-02T10:36:08.404-07:00My views on NeoHaskell<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="My views on NeoHaskell">
<meta name="twitter:description" content="A rundown and critique of the NeoHaskell project">
<title>My views on NeoHaskell</title>
<style>
html {
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 12px;
}
h1 {
font-size: 1.8em;
}
}
@media print {
html {
background-color: white;
}
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
svg {
height; auto;
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
font-size: 85%;
margin: 0;
hyphens: manual;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>Recently <a href="https://github.com/NickSeagull">Nick Seagull</a>
has <a
href="https://dev.to/neohaskell/introducing-neohaskell-a-beacon-of-joy-in-a-greyed-tech-world-4f9b">announced</a>
a <a href="https://neohaskell.org/">NeoHaskell</a> project which (I
believe) has generated some controversy. My first run-in with NeoHaskell
was <a
href="https://cohost.org/ratherforky/post/2952574-utterly-overwhelmed">this
post on cohost</a> criticizing the NeoHaskell project and a few of my
friends within the Haskell community have expressed concern about the
NeoHaskell project. My gut reaction is also critical, but I wanted to do
a more thorough investigation before speaking publicly against
NeoHaskell so I figured I would dig into the project more first. Who
knows, maybe my gut reaction is wrong? 🤷🏻♀️</p>
<p>Another reason NeoHaskell is relevant to me is that I think a lot
about marketing and product management for the Haskell community, and
even presented a talk on <a
href="https://www.youtube.com/watch?v=fNpsgTIpODA">How to market Haskell
mainstream programmers</a> so I’m particularly keen to study NeoHaskell
through that lens to see if he is trying to approach things in a similar
way or not.</p>
<p>I also have credentials to burnish in this regard. I have a lot of
experience with product management and technical product management for
open source projects via my work on Dhall. Not only did I author the
original implementation of Dhall but I singlehandedly built most of the
language ecosystem (including the language standard, documentation,
numerous language bindings, and the command-line tooling) and mentored
others to do the same.</p>
<p>Anyway, with that out of the way, on to NeoHaskell:</p>
<h3 id="what-is-neohaskell">What is NeoHaskell?</h3>
<p>I feel like this is probably the most important question to answer
because unless there is a clear statement of purpose for a project
there’s nothing to judge; it’s “not even wrong” because there’s no
yardstick by which to measure it and nothing to challenge.</p>
<p>So what <em>is</em> NeoHaskell?</p>
<p>I’ll break this into two parts: what NeoHaskell is <em>right now</em>
and what NeoHaskell aspires to be.</p>
<p>Based on what I’ve gathered, <em>right now</em> NeoHaskell is:</p>
<ul>
<li><p>A set of user experience requirements</p>
<p>… tracked as <a
href="https://github.com/neohaskell/NeoHaskell/issues">issues in this
GitHub repository</a></p></li>
<li><p>A <a href="https://neohaskell.org/">website</a></p>
<p>… which informally summarizes some of the headline requirements (such
as Python interop and mobile support)</p></li>
<li><p>An <a
href="https://dev.to/neohaskell/introducing-neohaskell-a-beacon-of-joy-in-a-greyed-tech-world-4f9b">announcement
post</a></p></li>
</ul>
<p>However, it’s not clear what NeoHaskell aspires to be from studying
the website, the issue tracker, or announcement:</p>
<ul>
<li><p>Is this going to be a new programming language inspired by
Haskell?</p>
<p>In other words, will this be a “clean room” implementation of a
language which is Haskell-like?</p></li>
<li><p>… or this going to be a fork of Haskell (more specifically:
<code>ghc</code>) to add the desired features?</p>
<p>In other words, will the relationship of NeoHaskell to Haskell be
similar to the relationship between NeoVim and Vim? (The name seems to
suggest as much)</p></li>
<li><p>… or this going to be changes to the command-line Haskell
tooling?</p>
<p>In other words, will this be kind of like <code>stack</code> and
promote a new suite of tools for doing Haskell development?</p></li>
<li><p>… or this going to be improvements to the Haskell package
ecosystem?</p>
<p>In other words, will this shore up and/or revive some existing
packages within the Haskell ecosystem?</p></li>
</ul>
<p>Here’s what I <strong>think</strong> NeoHaskell aspires to be based
on carefully reading through the website and all of the issues in the
issue tracker and drawing (I believe) reasonable inferences:</p>
<p>NeoHaskell is not going to be a fork of <code>ghc</code> and is
instead proposing to implement the following things:</p>
<ul>
<li>A new command-line tool (<code>neo</code>) similar in spirit to
<code>stack</code>
<ul>
<li>It is proposing some new features not present in <code>stack</code>
but it reads to me as similar to <code>stack</code>.</li>
</ul></li>
<li>A GHC plugin that would add:
<ul>
<li>new language features (none proposed so far, but it aims to be a
Haskell dialect)</li>
<li>improved error messages</li>
<li>some improvements to the UX (e.g. automatic hole filling)</li>
</ul></li>
<li>An attempt to revive the work on a mobile (ARM) backend for
Haskell</li>
<li>An overhaul of Haskell’s standard libraries similar in spirit to
<code>foundation</code></li>
<li><code>TemplateHaskell</code> support for the <code>cpython</code>
package for more ergonomic Python interop</li>
<li>A set of documentation for the language and some parts of the
ecosystem</li>
<li>An event sourcing framework
<ul>
<li>… and a set of template applications based on that framework</li>
</ul></li>
</ul>
<p>And in addition to that concrete roadmap Nick Seagull is essentially
proposing the following governance model for the NeoHaskell project (and
possibly the broader Haskell ecosystem if NeoHaskell gains
traction):</p>
<ul>
<li><p>Centralizing product management in himself as a benevolent
dictator</p>
<p>I don’t believe I’m exaggerating this. Here is the relevant excerpt
from the announcement post, which explicitly references the BDFL
model:</p>
<blockquote>
<p>I believe that in order for a product to be successful, the design
process <strong>must be centralized in a single person.</strong> This
person must listen to the users, the other designers, and in general
must have an open mind to always cherry-pick all possible ideas in order
to improve the product. <strong>I don’t believe that a product should be
guided by democracy</strong>, and neither it should implement all
suggestions by every user. In other words, I’ll be the one in charge of
generating and listening to discussions, and prioritizing the features
of the project.</p>
<p><strong>I understand that this comes with some risk</strong>, but at
the same time I believe that all programming tools like Python and Ruby
that are very loved by their communities are like that because of the
BDFL model</p>
</blockquote></li>
<li><p>Organizing work via the NeoHaskell discord and NeoHaskell GitHub
issue tracker</p></li>
</ul>
<p>I feel like it should have been easier to gather this concrete
information about NeoHaskell’s aspirational goals, if only so that the
project is less about vibes and more a discussion on a concrete
roadmap.</p>
<p>Alright, so now I’ll explain my general impression of this project.
I’ll start with the positive feedback followed by the negative feedback
and I’ll be a bit less reserved and more emotionally honest in my
feedback.</p>
<h3 id="positive-feedback">Positive feedback</h3>
<h4 id="welcome-contributions">Welcome contributions</h4>
<p>I’m not the kind of person who will turn down someone willing to do
work to make things better as long as they don’t make things worse. A
new mobile backend for Haskell sounds great! Python interop using
<code>TemplateHaskell</code> sounds nice! Documentation? Love it!</p>
<h4 id="a-ghc-plugin-is-a-good-approach">A GHC plugin is a good
approach</h4>
<p>I think the approach of implementing this as a GHC plugin is a much
better idea than forking <code>ghc</code>. This sidesteps the ludicrous
amount of work that would be required to maintain a fork of
<code>ghc</code>.</p>
<p>Moreover, implementing any Haskell dialect as a GHC plugin actually
minimizes ecosystem fragmentation because (similar to an alternate
Prelude) it doesn’t “leak”. If one of your dependencies uses a GHC
plugin for the NeoHaskell dialect then your package doesn’t have to use
that same dialect (you can still build that dependency and code your
package in non-Neo Haskell). <code>cabal</code> can handle that sort of
thing transparently.</p>
<h4 id="haskell-does-need-better-product-management">Haskell does need
better product management</h4>
<p>I think the <a href="https://haskell.foundation/">Haskell
foundation</a> was supposed to be this (I could be wrong) but that
didn’t really seem to pan out.</p>
<p>Either way, I think a lot of us know what good product management is
and it is strikingly absent from the ecosystem.</p>
<h3 id="negative-feedback">Negative feedback</h3>
<h4 id="benevolent-dictator">Benevolent dictator</h4>
<p>I think it’s ridiculous that someone who hasn’t made significant
contributions to the Haskell ecosystem wants to become a benevolent
dictator for a project aspiring to make an outsized impact on the
Haskell ecosystem. I know that this is harsh and a personal attack on
Nick and I’m also mindful that there’s a real person behind the avatar.
<strong>HOWEVER,</strong> when you propose to be a benevolent dictator
you are inherently <em>making things personal</em>. A proposal to become
a benevolent dictator is essentially a referendum on you as a person.<a
href="#fn1" class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a></p>
<p>And it’s not just a matter of fairness or whatever. Nick’s lack of
Haskell credentials directly impact his ability to actually meaningfully
improve upon prior art if he doesn’t understand the current state of the
art. Like, when Michael Snoyman created <code>stack</code> it did lead
to a lot of fragmentation in the Haskell tooling but at least I felt
like he was justified in his attempt because he had an impressive track
record and a deep understanding of the Haskell ecosystem and
toolchain.</p>
<p>I do not get anything remotely resembling that impression from Nick
Seagull. He strikes me as a dilettante in this area and not just due to
his lack of Haskell credentials but also due to some of his questionable
proposed changes. This brings me to:</p>
<h4 id="unwelcome-contributions">Unwelcome contributions</h4>
<p>Not all contributions benefit the ecosystem<a href="#fn2"
class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>. I
think proposing a new <code>neo</code> build tool is likely to fragment
the tooling in a way similar to <code>stack</code>. I have worked pretty
extensively with all three of <code>cabal</code>, <code>stack</code> and
Nix throughout my career and my intuition based on that experience is
that the only improvement to the Haskell command-line experience that is
viable and that will “win” in the long run is one that is directly
upstreamed into <code>cabal</code>. It’s just that nobody wants to do
that because it’s not as glamorous as writing your own build tool.</p>
<p>Similarly, I think his proposed vision of “event source all the
Haskell applications” (including <a
href="https://github.com/neohaskell/NeoHaskell/issues/39">command-line
scripts</a>) is poorly thought out. I firmly subscribe to the <a
href="https://en.wikipedia.org/wiki/Rule_of_least_power">principle of
least power</a> which says that you should use the simplest type or
abstraction available that gets the job done instead of trying to
shoehorn everything into the same “god type” or “god abstraction”. I
learned this the hard way when I tried to shoehorn everything into my <a
href="https://hackage.haskell.org/package/pipes"><code>pipes</code></a>
package and realized that it was a huge mistake, so it’s not like I’m
innocent in this regard. Don’t make the same mistake I did.</p>
<p>And it <em>matters</em> that some of these proposed changes are
counterproductive because if he indeed plays a role as a benevolent
dictator you’re not going to get to pick and choose which changes to
keep and which changes to ignore. You’re getting the whole package, like
it or not.</p>
<h4 id="not-good-product-management">Not good product management</h4>
<p>I don’t believe NeoHaskell is the good product management we’re all
looking for. “Haskell dialect + python interop + event sourcing + mobile
backend” is not a product. It’s an odd bundle of features that don’t
have a clear market or vertical or use case to constrain the design and
navigate tradeoffs. The NeoHaskell roadmap comes across to me as a grab
bag of unrelated features which individually sound good but that is not
necessarily good product management.</p>
<p>To make this concrete: what is the purpose of bundling both python
interop and a mobile backend into NeoHaskell’s roadmap? As far as I know
there is no product vertical that requires both of those things.</p>
<h4 id="the-overall-vibe-is-bad">The overall vibe is bad</h4>
<p>My initial impression of NeoHaskell was that it struck me as
bullshit. Carefully note that I’m not saying that Nick is a bullshitter,
but if he wants to be taken seriously then he needs to rethink how he
presents his ideas. Everything from the tone of the announcement post
(including the irrelevant AI-generated images), the complete absence of
any supporting code or mockups, and the wishy washy statement of purpose
all contributed to the non-serious vibes.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Anyway, I don’t hate Nick and I’m pretty sure I’d get along with him
great in person in other contexts. He also seems like a decently
accomplished guy in other respects. However, I think nominating himself
as a benevolent dictator for an ambitious ecosystem is a bit
irresponsible. However, we all make mistakes and can learn from
them.</p>
<p>And I don’t endorse NeoHaskell. I don’t think it’s any more likely to
succeed than Haskell absent some better product management. “I like
simple Haskell tailored to blue collar engineers” is a nice vibe but
it’s not a product.</p>
<aside id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>This is one of many reasons I never wanted to become
benevolent dictator of Dhall despite how often people try to make me
one. One of the first things I did when Dhall got adoption was to set up
an egalitarian governance structure.<a href="#fnref1"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>I don’t think it’s bad for people to just publish open
source projects, but when they attempt to rally social support and
gather mindshare for their projects then in my view there is potential
for harm if they’re not thought through since social structures are
sticky.<a href="#fnref2" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</aside>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-45466806161522108232023-09-08T07:57:00.003-07:002023-09-19T14:16:37.321-07:00GHC plugin for HLint<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:creator" content="@GabriellaG439" />
<meta name="twitter:title" content="GHC plugin for HLint" />
<meta name="twitter:description" content="An announcement post for a maintained GHC plugin for HLint" />
<title>GHC plugin for HLint</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>At <a href="https://github.com/MercuryTechnologies">work</a> I was
recently experimenting with running <a
href="https://github.com/ndmitchell/hlint#hlint---"><code>hlint</code></a>
(the widely used Haskell linting program) as a <a
href="https://downloads.haskell.org/ghc/latest/docs/users_guide/extending_ghc.html#compiler-plugins">GHC
plugin</a>. One reason why I was interested in this is because we have a
large (6000+ module) Haskell codebase at work, and I wanted to see if
this would make it cheaper to run <code>hlint</code> on our codebase.
Ultimately it did not work out but I built something that we could open
source so I polished it up and released it in case other people find it
useful. You can find the plugin (named <code>hlint-plugin</code>) on <a
href="https://hackage.haskell.org/package/hlint-plugin">Hackage</a> and
on <a
href="https://github.com/MercuryTechnologies/hlint-plugin">GitHub</a>.</p>
<p>This post will explain the background and motivation behind this work
to explain why such a plugin might be potentially useful to other
Haskell users.</p>
<h4 id="introduction-to-hlint">Introduction to <code>hlint</code></h4>
<p>If you’ve never heard of <code>hlint</code> before, it’s a Haskell
source code linting tool that is pretty widely used in the Haskell
ecosystem. For example, if you run <code>hlint</code> on the following
Haskell file:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> (<span class="fu">mempty</span>)</span></code></pre></div>
<p>… then you’ll get the following <code>hlint</code> error message:</p>
<pre><code>Main.hs:2:8-15: Warning: Redundant bracket
Found:
(mempty)
Perhaps:
mempty
1 hint</code></pre>
<p>… telling the user to remove the parentheses<a href="#fn1"
class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>
from around the <code>mempty</code>.</p>
<h4 id="integrating-hlint">Integrating <code>hlint</code></h4>
<p>However, <code>hlint</code> is a tool that is not integrated into the
compiler, meaning that you have to run it out of band from compilation
for it to catch errors. There are a few ways that one can fix this,
though:</p>
<ul>
<li><p>Create a script that builds your program and then runs
<code>hlint</code></p>
<p>This is the simplest possible thing that one can do, but it works and
some people do this. It’s the “low-tech” solution.</p></li>
<li><p>Use <a
href="https://github.com/haskell/haskell-language-server"><code>haskell-language-server</code></a>
or some IDE that plugin that auto-runs <code>hlint</code></p>
<p>This is a bit nicer for developers because now they can get rapid
feedback (in their editor) as they are authoring the code. For example,
<code>haskell-language-server</code> supports an <a
href="https://github.com/haskell/haskell-language-server/tree/master/plugins/hls-hlint-plugin#hlint-plugin-for-the-haskell-language-server"><code>hlint</code>
plugin</a><a href="#fn2" class="footnote-ref" id="fnref2"
role="doc-noteref"><sup>2</sup></a> for this purpose.</p></li>
<li><p>A GHC plugin (what this post is about)</p>
<p>If you turn <code>hlint</code> into a GHC plugin, then
<strong>ALL</strong> GHC-based Haskell tools automatically incorporate
<code>hlint</code> suggestions. For example, <a
href="https://github.com/ndmitchell/ghcid"><code>ghcid</code></a> would
automatically include <code>hlint</code> suggestions in its output,
something that doesn’t work with other approaches to integrate
<code>hlint</code>. Similarly, all <code>cabal</code> commands
(including <code>cabal build</code> and <code>cabal repl</code>) and all
<code>stack</code> commands benefit from a GHC plugin.</p></li>
</ul>
<h4 id="alternatives">Alternatives</h4>
<p>I’m not the first person who had this idea of turning
<code>hlint</code> into a GHC plugin. The first attempt to do this was
<a
href="https://github.com/ocharles/hlint-source-plugin"><code>hlint-source-plugin</code></a>,
but that was a pretty low-tech solution; it basically ran
<code>hlint</code> as an executable on the Haskell source file being
processed even though the GHC plugin already has access to the parsed
syntax tree.</p>
<p>The second attempt was the <a
href="https://github.com/tfausak/splint"><code>splint</code></a>
package. This GHC plugin was really well done (it’s basically exactly
how I envisioned this was supposed to work) and the corresponding <a
href="https://taylor.fausak.me/2020/05/25/running-hlint-as-a-ghc-source-plugin/">announcement
post</a> does a great job of motivating why <code>hlint</code> benefits
from being run as a GHC plugin.</p>
<p>However, the problem is that the <code>splint</code> package was
recently abandoned and the last version of GHC it supports is GHC 9.2.
Since we use GHC 9.6 at work I decided to essentially revive the
<code>splint</code> package so I created the <code>hlint-plugin</code>
package which is essentially the successor to <code>splint</code>.</p>
<h4 id="improvements">Improvements</h4>
<p><code>hlint-plugin</code> is not too different from what
<code>splint</code> did, but the main improvements that
<code>hlint-plugin</code> brings are:</p>
<ul>
<li><p>Support for newer versions of GHC</p>
<p><code>splint</code> supports GHC versions 8.10, 9.0, and 9.2 whereas
<code>hlint-plugin</code> supports GHC versions 9.0, 9.2, 9.4, and
9.6.</p></li>
<li><p>Known-good <code>cabal</code>/<code>stack</code>/<code>nix</code>
builds for the plugin</p>
<p>… see the next section for more details.</p></li>
<li><p>A test suite to verify that the plugin works</p>
<p><a
href="https://github.com/MercuryTechnologies/hlint-plugin/blob/6dad879364a338748c53ef3a9f2b9742fb3cd054/flake.nix#L93-L126"><code>hlint-plugin</code>’s
CI</a> actually checks that the plugin works for all supported versions
of GHC.</p></li>
<li><p>A simpler work-around to <a
href="https://gitlab.haskell.org/ghc/ghc/issues/18261">GHC issue
#18261</a></p>
<p>Basically, I independently stumbled upon the exact same problem that
<code>splint</code> encountered, but worked around it in a simpler way.
I won’t go into too much detail here other than to point out that you
can compare how <a
href="https://github.com/tfausak/splint/blob/9028a8b631568dc5d16a74153b1a9b6e3cde0fe6/src/lib/Splint/Settings.hs"><code>splint</code>
works around this bug</a> with how <a
href="https://github.com/MercuryTechnologies/hlint-plugin/blob/main/src/HLint/Plugin/Settings.hs"><code>hlint-plugin</code>
works around the bug</a>.</p></li>
</ul>
<p>Also, when stress testing <code>hlint-plugin</code> on our internal
codebase I discovered <a
href="https://github.com/ndmitchell/hlint/pull/1538">an
<code>hlint</code> bug</a> which affected some of our modules, and fixed
that, so the fix will be in the next release of <code>hlint</code>.</p>
<h4 id="tricky-build-stuff">Tricky build stuff</h4>
<p>Unfortunately, both <code>splint</code> and <code>hlint-plugin</code>
are tricky to correctly install. Why? Because, by default
<code>hlint</code> (and <code>ghc-lib-parser-ex</code>) use the
<code>ghc-lib</code> and <code>ghc-lib-parser</code> packages by default
instead of the <code>ghc</code> API. This is actually a pain in the ass
because a GHC plugin needs to be created using the <code>ghc</code> API
(i.e. it needs to be a value of type
<code>ghc:GHC.Plugins.Plugin</code>). Like, you can use
<code>hlint</code> to create a <code>ghc-lib:GHC.Plugins.Plugin</code>
and everything will type-check and build, but then when you try to
actually run the plugin it will fail.</p>
<p>There is a way to get <code>hlint</code> and
<code>ghc-lib-parser-ex</code> to use the <code>ghc</code> API, though!
However, you have to build them with non-default <code>cabal</code>
configure flags. Specifically, you have to configure <code>hlint</code>
with the <code>-f-ghc-lib</code> option and configure
<code>ghc-lib-parser-ex</code> with the <code>-fno-ghc-lib</code>
option.</p>
<p>To ease things for users I provided a <code>cabal.project</code> file
and a <code>flake.nix</code> file<a href="#fn4" class="footnote-ref"
id="fnref4" role="doc-noteref"><sup>4</sup></a> with working builds for
<code>hlint-plugin</code> that set all the correct configuration
options.</p>
<h4 id="performance">Performance</h4>
<p>I mentioned in the introduction that I was hoping for some
performance improvements from switching to a plugin but those
improvements didn’t materialize. I’ll talk a bit about what I
<em>thought</em> would work and why it didn’t pan out for us (even
though it still might help for you).</p>
<p>So there are up to three ways that <code>hlint</code> could
potentially be faster as a GHC plugin:</p>
<ul>
<li><p>Not having to re-lint modules that haven’t changed</p>
<p>This is nice (especially when your codebase has 6000+ modules like
ours). When you turn <code>hlint</code> into a GHC plugin you only run
it whenever GHC recompiles a module and you don’t have to run
<code>hlint</code> over your entire codebase after every change.</p>
<p>However, this was actually not a significant benefit to our company
because we already have some scripts which take care of only running
<code>hlint</code> on the modules that have changed (according to
<code>git</code>). However, it’s still a “nice to have” because it’s
architecturally simpler (no need to write that clever script if GHC can
take care of detecting changes for us).</p></li>
<li><p>Not having to parse the Haskell code twice</p>
<p>This is likely a minor performance improvement since parsing is (in
my experience) typically not the bottleneck for compiling Haskell
code.</p></li>
<li><p>Running <code>hlint</code> while GHC is compiling modules</p>
<p>What I mean by this is that if <code>hlint</code> is a GHC plugin
then it can begin running while the GHC build is ongoing! In large
builds (like ours) there are often a large number of cores that go
unused and the <code>hlint</code> plugin could potentially exploit those
idle cores to do work before the build is done.</p>
<p>However, in practice this benefit did <em>not</em> pan out and our
build didn't really get faster when we enabled
<code>hlint-plugin</code>. The time it took to build our codebase with
the plugin was essentially the same amount of time as running <code>hlint</code>
in a separate step.</p></li>
</ul>
<h4 id="future-directions">Future directions</h4>
<p>The <a
href="https://github.com/ocharles/hlint-source-plugin#future-work"><code>hlint-source-plugin</code>
repository</a> notes that if <code>hlint</code> were implemented as a
GHC plugin (which it now is) then it would fix some of the hacks that
<code>hlint</code> has to use:</p>
<blockquote>
<p>Currently this plugin simply hooks into the parse stage and calls
HLint with a file path. This means HLint will re-parse all source code.
The next logical step is to use the actual parse tree, as given to us by
GHC, and HLint that. This means that HLint can lose the special logic to
run CPP, along with the hacky handling of fixity resolution (we get that
done correctly by GHC’s renaming phase).</p>
</blockquote>
<p>… because of this I sort of feel that <code>hlint</code> really
should be a GHC plugin. It’s understandable why <code>hlint</code> was
not initially implemented in this way (since I believe the GHC plugin
system didn’t exist back then), but now it sort of feels like a GHC
plugin is a much more natural way of integrating <code>hlint</code>.</p>
<section class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1" role="doc-endnote"><p>I refuse to call parentheses
“brackets”.<a href="#fnref1" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn2" role="doc-endnote"><p>Note that this is a plugin for
<code>haskell-language-server</code>, which is a different type of
plugin than a GHC plugin. A <code>haskell-language-server</code> plugin
only works with <code>haskell-language-server</code> whereas a GHC
plugin works with anything that uses GHC. The two types of plugins are
also installed and set up in different ways.<a href="#fnref2"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3" role="doc-endnote"><p>Note that this is a plugin for
<code>haskell-language-server</code>, which is a different type of
plugin than a GHC plugin. A <code>haskell-language-server</code> plugin
only works with <code>haskell-language-server</code> whereas a GHC
plugin works with anything that uses GHC. The two types of plugins are
also installed and set up in different ways.<a href="#fnref3"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4" role="doc-endnote"><p>I tried to create a working
<code>stack.yaml</code> and failed to get it working, but I’d accept a
pull request adding a working <code>stack</code> build if someone else
has better luck than I did.<a href="#fnref4" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com3tag:blogger.com,1999:blog-1777990983847811806.post-8367549834961049182023-04-03T07:19:00.002-07:002023-04-03T07:33:20.990-07:00Ergonomic newtypes for Haskell strings and numbers<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="Ergonomic newtypes for Haskell strings and numbers">
<meta name="twitter:description" content="A trick to elide newtypes around Haskell strings and numbers">
<title>Ergonomic newtypes for Haskell strings and numbers</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This blog post summarizes a very brief trick I commonly recommend
whenever I see something like this:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Numeric.Natural</span> (<span class="dt">Natural</span>)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">Text</span> }</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Age</span> <span class="ot">=</span> <span class="dt">Age</span> {<span class="ot"> getAge ::</span> <span class="dt">Natural</span> }</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Person</span> <span class="ot">=</span> <span class="dt">Person</span> {<span class="ot"> name ::</span> <span class="dt">Name</span>,<span class="ot"> age ::</span> <span class="dt">Age</span> }</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> <span class="dt">Name</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dt">Age</span> <span class="dv">42</span> }</span></code></pre></div>
<p>… where the <code>newtype</code>s are not opaque (i.e. the
<code>newtype</code> constructors are exported), so the
<code>newtype</code>s are more for documentation purposes rather than
type safety.</p>
<p>The issue with the above code is that the <code>newtype</code>s add
extra boilerplate for both creating and displaying those types. For
example, in order to create the <code>Name</code> and <code>Age</code>
<code>newtype</code>s you need to explicitly specify the
<code>Name</code> and <code>Age</code> constructors (like in the
definition for <code>example</code> above) and they also show up when
displaying values for debugging purposes (e.g. in the REPL):</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> example</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Person</span> {name <span class="ot">=</span> <span class="dt">Name</span> {getName <span class="ot">=</span> <span class="st">"John Doe"</span>}, age <span class="ot">=</span> <span class="dt">Age</span> {getAge <span class="ot">=</span> <span class="dv">42</span>}}</span></code></pre></div>
<p>Fortunately, you can easily elide these noisy constructors if you
follow these rules of thumb:</p>
<ul>
<li><p>Derive <code>IsString</code> for <code>newtype</code>s around
string-like types</p></li>
<li><p>Derive <code>Num</code> for <code>newtype</code>s around numeric
types</p></li>
<li><p>Change the <code>Show</code> instances to use the underlying
<code>Show</code> for the wrapped type</p></li>
</ul>
<p>For example, I would suggest amending the original code like
this:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DerivingStrategies #-}</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE GeneralizedNewtypeDeriving #-}</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Example1</span> <span class="kw">where</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String</span> (<span class="dt">IsString</span>)</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Numeric.Natural</span> (<span class="dt">Natural</span>)</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">Text</span> }</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">IsString</span>, <span class="dt">Show</span>)</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Age</span> <span class="ot">=</span> <span class="dt">Age</span> {<span class="ot"> getAge ::</span> <span class="dt">Natural</span> }</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">Num</span>, <span class="dt">Show</span>)</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Person</span> <span class="ot">=</span> <span class="dt">Person</span> {<span class="ot"> name ::</span> <span class="dt">Name</span>,<span class="ot"> age ::</span> <span class="dt">Age</span> }</span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dv">42</span> }</span></code></pre></div>
<p>… and now the <code>Age</code> and <code>Name</code> constructors are
invisible, even when displaying these types (using their
<code>Show</code> instances):</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> example</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Person</span> {name <span class="ot">=</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dv">42</span>}</span></code></pre></div>
<p>That is the entirety of the trick, but if you still don’t follow,
I’ll expand upon that below.</p>
<h4 id="explanation">Explanation</h4>
<p>Revisiting the starting code:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Numeric.Natural</span> (<span class="dt">Natural</span>)</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">Text</span> }</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Age</span> <span class="ot">=</span> <span class="dt">Age</span> {<span class="ot"> getAge ::</span> <span class="dt">Natural</span> }</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Person</span> <span class="ot">=</span> <span class="dt">Person</span> {<span class="ot"> name ::</span> <span class="dt">Name</span>,<span class="ot"> age ::</span> <span class="dt">Age</span> }</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> <span class="dt">Name</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dt">Age</span> <span class="dv">42</span> }</span></code></pre></div>
<p>… the first thing we’re going to do is to enable the
<code>DerivingStrategies</code> language extension because I’m going to
lean pretty heavily on Haskell’s support for deriving typeclass
instances in this post and I want to be more explicit about how these
instances are being derived:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DerivingStrategies #-}</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">Text</span> }</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Age</span> <span class="ot">=</span> <span class="dt">Age</span> {<span class="ot"> getAge ::</span> <span class="dt">Natural</span> }</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span></code></pre></div>
<p>I’ve changed the code to explicitly specify that we’re
<code>deriving</code> <code>Show</code> using the “<code>stock</code>”
deriving strategy, meaning that Haskell has built-in language support
for deriving <code>Show</code> and we’re going to use that.</p>
<p>The next step is that we’re going to add an <code>IsString</code>
instance for <code>Name</code> because it wraps a string-like type
(<code>Text</code>). However, at first we’ll write out the instance by
hand:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String</span> (<span class="dt">IsString</span>(..))</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">IsString</span> <span class="dt">Name</span> <span class="kw">where</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> fromString string <span class="ot">=</span> <span class="dt">Name</span> (fromString string)</span></code></pre></div>
<p>This <code>IsString</code> instance works in conjunction with
Haskell’s <code>OverloadedStrings</code> so that we can directly use a
string literal in place of a <code>Name</code>, like this:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dt">Age</span> <span class="dv">42</span> }</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ↑</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="co">-- No more Name constructor required here</span></span></code></pre></div>
<p>… and the reason that works is because the compiler implicitly
inserts <code>fromString</code> around all string literals when you
enable <code>OverloadedStrings</code>, as if we had written this:</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> fromString <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dt">Age</span> <span class="dv">42</span> }</span></code></pre></div>
<p>The <code>IsString</code> instance for <code>Name</code>:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">IsString</span> <span class="dt">Name</span> <span class="kw">where</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> fromString string <span class="ot">=</span> <span class="dt">Name</span> (fromString string)</span></code></pre></div>
<p>… essentially defers to the <code>IsString</code> instance for the
underlying wrapped type (<code>Text</code>). In fact, this pattern of
deferring to the underlying instance is common enough that Haskell
provides a language extension for this purpose:
<code>GeneralizedNewtypeDeriving</code>. If we enable that language
extension, then we can simplify the <code>IsString</code> instance to
this:</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE GeneralizedNewtypeDeriving #-}</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">Text</span> }</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">IsString</span>)</span></code></pre></div>
<p>The <code>deriving newtype</code> indicates that we’re explicitly
using the <code>GeneralizedNewtypeDeriving</code> extension to derive
the implementation for the <code>IsString</code> instance.</p>
<p>In this particular case we don’t have to specify the deriving
strategy; we could have just said <code>deriving (IsString)</code> and
it still would have worked because it wasn’t ambiguous; no other
deriving strategy would have worked in this case. However, as we’re
about to see there are cases where you want to explicitly disambiguate
between multiple possible deriving strategies.</p>
<p>The next step is that we implement <code>Num</code> for our
<code>Age</code> type since it wraps a numeric type
(<code>Natural</code>):</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Num</span> <span class="dt">Age</span> <span class="kw">where</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">Age</span> x <span class="op">+</span> <span class="dt">Age</span> y <span class="ot">=</span> <span class="dt">Age</span> (x <span class="op">+</span> y)</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">Age</span> x <span class="op">-</span> <span class="dt">Age</span> y <span class="ot">=</span> <span class="dt">Age</span> (x <span class="op">-</span> y)</span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">Age</span> x <span class="op">*</span> <span class="dt">Age</span> y <span class="ot">=</span> <span class="dt">Age</span> (x <span class="op">*</span> y)</span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a> <span class="fu">negate</span> (<span class="dt">Age</span> x) <span class="ot">=</span> <span class="dt">Age</span> (<span class="fu">negate</span> x)</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">abs</span> (<span class="dt">Age</span> x) <span class="ot">=</span> <span class="dt">Age</span> (<span class="fu">abs</span> x)</span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a> <span class="fu">signum</span> (<span class="dt">Age</span> x) <span class="ot">=</span> <span class="dt">Age</span> (<span class="fu">signum</span> x)</span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a> <span class="fu">fromInteger</span> integer <span class="ot">=</span> <span class="dt">Age</span> (<span class="fu">fromInteger</span> integer)</span></code></pre></div>
<p>Bleh! That’s a lot of work to do when really we were most interested
in the <code>fromInteger</code> method (so that we could use numeric
literals directly to create an <code>Age</code>).</p>
<p>The reason we care about the <code>fromInteger</code> method is
because Haskell lets you use integer literals for any type that
implements <code>Num</code> (without any language extension; this is
part of the base language). So, for example, we can further simplify our
<code>example</code> <code>Person</code> to:</p>
<div class="sourceCode" id="cb13"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dv">42</span> }</span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ↑</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a> <span class="co">-- No more Age constructor required here</span></span></code></pre></div>
<p>… and the reason that works is because the compiler implicitly
inserts <code>fromInteger</code> around all integer literals, as if we
had written this:</p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Person</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Person</span>{ name <span class="ot">=</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="fu">fromInteger</span> <span class="dv">42</span> }</span></code></pre></div>
<blockquote>
<p>It would be nice if Haskell had a dedicated class for just the
<code>fromInteger</code> method (e.g. <code>IsInteger</code>), but alas
if we want ergonomic support for numeric literals then we have to add
support for other numeric operations, too, even if they might not
necessarily make sense for our <code>newtype</code>.</p>
</blockquote>
<p>Like before, though, we can use the
<code>GeneralizedNewtypeDeriving</code> extension to derive
<code>Num</code> instead:</p>
<div class="sourceCode" id="cb15"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Age</span> <span class="ot">=</span> <span class="dt">Age</span> {<span class="ot"> getAge ::</span> <span class="dt">Natural</span> }</span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">Num</span>)</span></code></pre></div>
<p>Much better!</p>
<p>However, we’re not done, yet, because at the moment these
<code>Name</code> and <code>Age</code> constructors still appear in the
debug output:</p>
<div class="sourceCode" id="cb16"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> example</span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Person</span> {name <span class="ot">=</span> <span class="dt">Name</span> {getName <span class="ot">=</span> <span class="st">"John Doe"</span>}, age <span class="ot">=</span> <span class="dt">Age</span> {getAge <span class="ot">=</span> <span class="dv">42</span>}}</span></code></pre></div>
<p>Yuck!</p>
<p>Okay, so the final step is to change the <code>Show</code> instances
for <code>Name</code> and <code>Age</code> to defer to the
<code>Show</code> instances for their underlying types:</p>
<div class="sourceCode" id="cb17"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Show</span> <span class="dt">Name</span> <span class="kw">where</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">show</span> (<span class="dt">Name</span> string) <span class="ot">=</span> <span class="fu">show</span> string</span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Show</span> <span class="dt">Age</span> <span class="kw">where</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">show</span> (<span class="dt">Age</span> natural) <span class="ot">=</span> <span class="fu">show</span> natural</span></code></pre></div>
<p>These are still valid <code>Show</code> instances! The
<code>Show</code> class requires that the displayed representation
should be valid Haskell code for creating a value of that type, and in
both cases that’s what we get.</p>
<p>For example, if you <code>show</code> a value like
<code>Name "John Doe"</code> you will get <code>"John Doe"</code>, and
that’s valid Haskell code for creating a <code>Name</code> if you enable
<code>OverloadedStrings</code>.</p>
<blockquote>
<p>Note: You might argue that this is not a valid <code>Show</code>
instance because it requires the use of a language extension
(e.g. <code>OverloadedStrings</code>) in order to be valid code.
However, this is no different than the <code>Show</code> instance for
<code>Text</code> (which is also only valid if you enable
<code>OverloadedStrings</code>), and most people do not take issue with
that <code>Show</code> instance for <code>Text</code> either.</p>
</blockquote>
<p>Similarly, if you <code>show</code> a value like <code>Age 42</code>
you will get <code>42</code>, and that’s valid Haskell code for creating
an <code>Age</code>.</p>
<p>So with those two new <code>Show</code> instances our
<code>Person</code> type now renders much more compactly:</p>
<div class="sourceCode" id="cb18"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> example</span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Person</span> {name <span class="ot">=</span> <span class="st">"John Doe"</span>, age <span class="ot">=</span> <span class="dv">42</span>}</span></code></pre></div>
<p>… but we’re not done! The last part of the trick is to use
<code>GeneralizedNewtypeDeriving</code> to derive the <code>Show</code>
instances, like this:</p>
<div class="sourceCode" id="cb19"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">Text</span> }</span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">IsString</span>, <span class="dt">Show</span>)</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Age</span> <span class="ot">=</span> <span class="dt">Age</span> {<span class="ot"> getAge ::</span> <span class="dt">Natural</span> }</span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">Num</span>, <span class="dt">Show</span>)</span></code></pre></div>
<p>… and this is where the <code>DerivingStrategies</code> language
extension really matters! Without that extension there would be no way
to tell the compiler to derive <code>Show</code> by deferring to the
underlying type. By default, if you don’t specify the deriving strategy
then the compiler assumes that derived <code>Show</code> instances use
the <code>stock</code> deriving strategy.</p>
<h4 id="conclusion">Conclusion</h4>
<p>There’s one last bonus to doing things in this way: you might now be
able to hide the <code>newtype</code> constructor by not exporting it! I
think this is actually the most important benefit of all because a
<code>newtype</code> with an exposed constructor doesn’t really improve
upon the type safety of the underlying type.</p>
<p>When a <code>newtype</code> like <code>Name</code> or
<code>Age</code> exposes the <code>newtype</code> constructor then the
<code>newtype</code> serves primarily as documentation and I’m not a big
fan of this “<code>newtype</code>s as documentation” design pattern.
However, I’m not that strongly opposed to it either; I wouldn’t use it
in own code, but I also wouldn’t insist that others don’t use it.
Another post which takes a stronger stance on this is <a
href="https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-type-safety/">Names
are not type safety</a>, especially the section on “Newtypes as
tokens”.</p>
<p>I’m personally okay with other people using <code>newtype</code>s in
this way, but if you do use “<code>newtype</code>s as documentation”
then please add <code>IsString</code> / <code>Num</code> /
<code>Show</code> instances as described in this post so that they’re
more ergonomic for others to use.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com1tag:blogger.com,1999:blog-1777990983847811806.post-29774318699600593792023-03-06T08:08:00.004-08:002023-03-06T08:08:45.451-08:00The "open source native" principle for software design<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="The "open source native" principle for software design">
<meta name="twitter:description" content="Design proprietary software as if it were open source software">
<title>The "open source native" principle for software design</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This post summarizes a software design principle I call the “open
source native” principle which I’ve invoked a few times as a technical
lead. I wanted to write this down so that I could easily reference this
post in the future.</p>
<p>The “open source native” principle is simple to state:</p>
<blockquote>
<p>Design proprietary software as if you intended to open source that
software, regardless of whether you will open source that software</p>
</blockquote>
<p>I call this the “open source native” principle because you design
your software as if it were a “native” member of the open source
ecosystem. In other words, your software is spiritually “born” open
source, aspirationally written from the beginning to be a good open
source citizen, even if you never actually end up open sourcing that
software.</p>
<p>You can’t always adhere to this principle, but I still use this as a
general design guideline.</p>
<h4 id="example">Example</h4>
<p>It’s hard to give a detailed example of this principle since most of
the examples I’d like to use are … well … proprietary and wouldn’t make
sense outside of their respective organizations. However, I’ll try to
outline a hypothetical example (inspired by a true story) that hopefully
enough can people can relate to.</p>
<p>Suppose that your organization provides a product with a
domain-specific programming language for customizing their product’s
behavior. Furthermore, suppose that you’re asked to design and implement
a package manager for this programming language.</p>
<p>There are multiple data stores you could use for storing packages,
but to simplify this example suppose there are only two options:</p>
<ul>
<li><p>Store packages in a product-specific database</p>
<p>Perhaps your product already uses a database for other reasons, so
you figure that you can reuse that existing database for storing
packages. That way you don’t need to set up any new infrastructure to
get going since the database team will handle that for you. Plus you get
the full powerful of a relational database so now you have powerful
tools for querying and/or modifying packages.</p></li>
<li><p>Store packages in <code>git</code></p>
<p>You might instead store your packages as flat files inside of a
<code>git</code> repository.</p></li>
</ul>
<p>These represent two extremes of the spectrum and in reality there
might be other options in between (like a standalone <code>sqlite</code>
database), but this is a contrived example.</p>
<p>According to the open source principle, you’d prefer to store
packages in <code>git</code> because <code>git</code> is a foundational
building block of the open source ecosystem that is already
battle-tested for this purpose. You’d be sacrificing some features
(you’d no longer have access to the full power of a relational
database), but your package manager would now be more “open-source
native”.</p>
<p>You might wonder: why would one deliberately constrain themselves
like that? What’s the benefit of designing things in this way if they
might never be open sourced?</p>
<h4 id="motivation">Motivation</h4>
<p>There are several reasons I espouse this design principle:</p>
<ul>
<li><p>better testability</p>
<p>If you design your component so that it’s easy to use outside of the
context of your product then it’s also easier to test in isolation. This
means that you don’t need to rely on heavyweight integration tests or
end-to-end tests to verify that your component works correctly.</p>
<p>For example, a package manager based on <code>git</code> is easier to
test than a package manager based on a database because a
<code>git</code> repository is easier to set up.</p></li>
<li><p>faster release cadence</p>
<p>If your component can be tested in isolation then you don’t even need
to share continuous integration (CI) with the rest of your organization.
Your component can have its own CI and release on whatever frequency is
appropriate for that component instead of coupling its release cadence
to the rest of your product.</p>
<p>That in turn typically means that you can <a
href="https://www.haskellforall.com/2019/05/release-early-and-often.html">release
earlier and more often</a>, which is a virtue in its own right.</p>
<p>Continuing the package manager example, you wouldn’t need to couple
releases of your package manager to the release cadence of the rest of
your product, so you’d be able to push out improvements or fixes more
quickly.</p></li>
<li><p>simpler documentation</p>
<p>It’s much easier to write a tutorial for software that delivers value
in isolation since there’s less supporting infrastructure necessary to
follow along with the tutorial.</p></li>
<li><p>well-chosen interfaces</p>
<p>You have to carefully think through the correct logical boundaries
for your software when you design for a broader audience of users. It’s
also easier to enforce stronger boundaries and narrower scope for the
same reasons.</p>
<p>For example, our hypothetical package manager is less likely to have
package metadata polluted with product-specific details if it is
designed to operate independently of the product.</p></li>
<li><p>improved stability</p>
<p>Open source software doesn’t just target a broader audience, but also
targets a broader time horizon. An open source mindset promotes thinking
beyond the needs of this financial quarter.</p></li>
<li><p>you can open source your component! (duh)</p>
<p>Needless to say, if you design your component to be open-source
native, it’s also easier to open source. Hooray! 🎉</p></li>
</ul>
<h4 id="conclusion">Conclusion</h4>
<p>You can think of this design principle as being similar to the <a
href="https://en.wikipedia.org/wiki/Rule_of_least_power">rule of least
power</a>, where you’re making your software less powerful (by adding
the additional constraint that it can be open sourced), but in turn
improving ease of comprehension, maintainability, and distribution.</p>
<p>Also, if you have any examples along these lines that you care to
share, feel free to drop them in the comments.</p>
</body>
</html>Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-73469814996949871602023-01-30T06:52:00.002-08:002023-01-30T06:54:36.962-08:00terraform-nixos-ng: Modern terraform support for NixOS<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="terraform-nixos-ng: Modern terraform support for NixOS">
<meta name="twitter:description" content="Announcement post for improved terraform bindings for NixOS">
<title>terraform-nixos-ng: Modern terraform support for NixOS</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>Recently I’ve been working on writing a <a
href="https://leanpub.com/nixos-in-production">“NixOS in Production”
book</a> and one of the chapters I’m writing is on deploying NixOS using
<code>terraform</code>. However, one of the issues I ran across was the
poor NixOS support for <code>terraform</code>. I’ve already gone through
the <a
href="https://nix.dev/tutorials/deploying-nixos-using-terraform"><code>nix.dev</code>
post</a> explaining how to use the <a
href="https://github.com/tweag/terraform-nixos"><code>terraform-nixos</code></a>
project but I ran into several issues trying to follow those
instructions (which I’ll explain below). That plus the fact that
<code>terraform-nixos</code> <a
href="https://github.com/tweag/terraform-nixos/issues/69">seems to be
unmaintained</a> pushed me over the edge to rewrite the project to
simplify and improve upon it.</p>
<p>So this post is announcing my <code>terraform-nixos-ng</code>
project:</p>
<ul>
<li><a href="https://github.com/Gabriella439/terraform-nixos-ng">GitHub
- <code>Gabriella439/terraform-nixos-ng</code></a></li>
</ul>
<p>… which is a rewrite of <code>terraform-nixos</code> and I’ll use
this post to compare and contrast the two projects. If you’re only
interested in trying out the <code>terraform-nixos-ng</code> project
then go straight to <a
href="https://github.com/Gabriella439/terraform-nixos-ng/blob/main/README.md">the
<code>README</code></a></p>
<h4 id="using-nixos-rebuild">Using <code>nixos-rebuild</code></h4>
<p>One of the first things I noticed when kicking the tires on
<code>terraform-nixos</code> was that it was essentially reinventing
what the <code>nixos-rebuild</code> tool already does. In fact, I was so
surprised by this that I wrote a standalone post explaining how to use
<code>nixos-rebuild</code> as a deployment tool:</p>
<ul>
<li><a
href="https://www.haskellforall.com/2023/01/announcing-nixos-rebuild-new-deployment.html">Haskell
for all - Announcing nixos-rebuild: a “new” deployment tool for
NixOS</a></li>
</ul>
<p>Simplifying that code using <code>nixos-rebuild</code> fixed lots of
tiny papercuts I had with <code>terraform-nixos</code>, like:</p>
<ul>
<li><p>The deploy failing if you don’t have a new enough version of
<code>bash</code> installed</p></li>
<li><p>The inability to turn off the use of the
<code>--use-substitutes</code> flag</p>
<p>That flag causes issues if you want to deploy to a machine that
disables outbound connections.</p></li>
<li><p>The dearth of useful options (compared to
<code>nixos-rebuild</code>)</p>
<p>… including the inability to fully customize <code>ssh</code>
options</p></li>
<li><p>The poor interop with flakes</p>
<p>For example, <code>terraform-nixos</code> doesn’t respect the <a
href="https://nixos.wiki/wiki/Flakes#Output_schema">standard
<code>nixosConfigurations</code> flake output hierarchy</a>.</p>
<p>Also, <code>terraform-nixos</code> doesn’t use flakes natively (it
uses <a
href="https://github.com/edolstra/flake-compat"><code>flake-compat</code></a>),
which breaks handling of the
<code>config.nix.binary{Caches,CachePublicKeys}</code> flakes settings.
The Nix UX for flakes is supposed to ask the user to consent to those
settings (because they are potentially insecure to auto-enable for a
flake), but their workaround breaks that UX by automatically enabling
those settings without the user’s consent.</p></li>
</ul>
<p>I wanted to upstream this rewrite to use <code>nixos-rebuild</code>
into <code>terraform-nixos</code>, but I gave up on that idea when I saw
that no pull request since 2021 had been merged, including conservative
pull requests like <a
href="https://github.com/tweag/terraform-nixos/pull/61/files">this
one</a> to just use the script included within the repository to update
the list of available AMIs.</p>
<p>That brings me to the next improvement, which is:</p>
<h4 id="auto-generating-available-amis">Auto-generating available
AMIs</h4>
<p>The <code>terraform-nixos</code> repository requires the AMI list to
be manually updated. The way you do this is to periodically run a script
to fetch the available AMIs from Nixpkgs and then create a PR to vendor
those changes. However, this shouldn’t be necessary because we could
easily program <code>terraform</code> to generate the list of AMIs on
the fly.</p>
<p>This is what the <code>terraform-nixos-ng</code> project does, where
the <code>ami</code> module creates a <a
href="https://github.com/Gabriella439/terraform-nixos-ng/blob/2f25ef9284ad9d45e21a7c35402d9593e2b9a768/ami/main.tf#L79-L88">data
source that runs an equivalent script</a> to fetch the AMIs at
provisioning time.</p>
<p>In the course of rewriting the AMI module, I made another small
improvement, which was:</p>
<h4 id="support-for-aarch64-amis">Support for <code>aarch64</code>
AMIs</h4>
<p>Another gripe I had with <code>terraform-nixos-ng</code> is that its
AMI module doesn’t support <code>aarch64-linux</code> NixOS AMIs even
though these AMIs exist and <a
href="https://github.com/NixOS/nixpkgs/blob/375d7f2ac02c2dee60d21aeda59f939cdd095d95/nixos/modules/virtualisation/amazon-ec2-amis.nix">Nixpkgs
supports them</a>. That was a small and easy fix, too.</p>
<h4 id="functionality-regressions">Functionality regressions</h4>
<p><code>terraform-nixos-ng</code> is not a strict improvement over
<code>terraform-nixos</code>, though. Specifically, the most notable
feature omissions are:</p>
<ul>
<li><p>Support for non-flake workflows</p>
<p><code>terraform-nixos-ng</code> requires the use of flakes and
doesn’t provide support for non-flake-based workflows. I’m very much on
team “Nix flakes are good and shouldn’t be treated as experimental any
longer” so I made an opinionated choice to require users to use flakes
rather than support their absence.</p>
<p>This choice also isn’t completely aesthetic, the use of flakes
improves interop with <code>nixos-rebuild</code>, where flakes are the
most ergonomic way for <code>nixos-rebuild</code> to select from one of
many deployments.</p></li>
<li><p>Support for secrets management</p>
<p>I felt that this should be handled by something like <a
href="https://github.com/Mic92/sops-nix"><code>sops-nix</code></a>
rather than rolling yet another secrets management system that was
idiosyncratic to this deploy tool. In general, I wanted these
<code>terraform</code> modules to be as lightweight as possible by
making more idiomatic use of the modern NixOS ecosystem.</p></li>
<li><p>Support for Google Compute Engine images</p>
<p><code>terraform-nixos</code> supports GCE images and the only reason
I didn’t add the same support is because I’ve never used Google Compute
Engine so I didn’t have enough context to do a good rewrite, nor did I
have the inclination to set up a GCE account just to test the rewrite.
However, I’d accept a pull request adding this support from someone
interested in this feature.</p></li>
</ul>
<h4 id="conclusion">Conclusion</h4>
<p>There’s one last improvement over the <code>terraform-nixos</code>
project, which is that I don’t leave projects in an abandoned state.
Anybody who has contributed to my open source projects knows that I’m
generous about handing out the commit bit and I’m also good about
relinquishing control if I don’t have time to maintain the project
myself.</p>
<p>However, I don’t expect this to be a difficult project to maintain
anyway because I designed <code>terraform-nixos-ng</code> to outsource
the work to existing tools as much as possible instead of reinventing
the wheel. This is why the implementation of
<code>terraform-nixos-ng</code> is significantly smaller than
<code>terraform-nixos</code>.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-69019174999439818692023-01-23T07:22:00.008-08:002023-01-23T09:20:17.872-08:00Announcing nixos-rebuild: a "new" deployment tool for NixOS<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="Announcing nixos-rebuild: a "new" deployment tool for NixOS">
<meta name="twitter:description" content="A post explaining how nixos-rebuild is a cross-platform tool that can deploy remote NixOS systems">
<title>Announcing nixos-rebuild: a "new" deployment tool for NixOS</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>The title of this post is tongue-in-cheek; <code>nixos-rebuild</code>
is a tool that has been around for a long time and there’s nothing new
about it. However, I believe that not enough people know how capable
this tool is for building and deploying <strong>remote</strong> NixOS
systems. In other words, <code>nixos-rebuild</code> is actually a decent
alternative to tools like <a
href="https://github.com/DBCDK/morph"><code>morph</code></a> or <a
href="https://github.com/zhaofengli/colmena"><code>colmena</code></a>.</p>
<p>Part of the reason why <code>nixos-rebuild</code> flies under the
radar is because it’s more commonly used for upgrading the current NixOS
system, rather than deploying a remote NixOS system. However, it’s
actually fairly capable of managing another NixOS system.</p>
<p>In fact, your local system (that initiates the deploy) doesn’t have
to be a NixOS system or even a Linux system. An even lesser known fact
is that you can initiate deploys from macOS using
<code>nixos-rebuild</code>. In other words, <code>nixos-rebuild</code>
is a cross-platform deploy tool!</p>
<h4 id="the-trick">The trick</h4>
<p>I’ll give a concrete example. Suppose that I have the following NixOS
configuration (for a blank EC2 machine) saved in
<code>configuration.nix</code>:</p>
<pre class="nix"><code>{ modulesPath, ... }:
{ imports = [ "${modulesPath}/virtualisation/amazon-image.nix" ];
system.stateVersion = "22.11";
}</code></pre>
<p>… which I’ve wrapped in the following flake (since I like Nix
flakes):</p>
<pre class="nix"><code>{ inputs.nixpkgs.url = "github:NixOS/nixpkgs/22.11";
outputs = { nixpkgs, ... }: {
nixosConfigurations.default = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
};
};
}</code></pre>
<p>Further suppose that I have an <code>x86_64-linux</code> machine on
EC2 accessible via <code>ssh</code> at <code>root@example.com</code>. I
can deploy that configuration to the remote machine like this:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix shell nixpkgs#nixos-rebuild</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nixos-rebuild switch <span class="at">--fast</span> <span class="at">--flake</span> .#default <span class="dt">\</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="at">--target-host</span> root@example.com <span class="dt">\</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="at">--build-host</span> root@example.com</span></code></pre></div>
<p>… and that will build and deploy the remote machine even if your
current machine is a completely different platform (e.g. macOS).</p>
<h4 id="why-this-works">Why this works</h4>
<p>The <code>--fast</code> flag is the first adjustment that makes the
above command work on systems other NixOS. Without that flag
<code>nixos-rebuild</code> will attempt to build itself for the target
platform and run that new executable with the same arguments, which will
fail if the target platform differs from your current platform.</p>
<p>The <code>--build-host</code> flag is also necessary if the source
and target platform don’t match. This instructs
<code>nixos-rebuild</code> to build on the target machine so that the
deploy is insensitive to your current machine’s platform.</p>
<p>The final thing that makes this work is that Nixpkgs makes the
<code>nixos-rebuild</code> script available on all platforms, despite
the script living underneath the <code>pkgs/os-specific/linux</code>
directory in Nixpkgs.</p>
<h4 id="flakes">Flakes</h4>
<p>There’s a reason why I suggest using flakes alongside
<code>nixos-rebuild</code>: with flakes you can specify multiple NixOS
machines within the same file (just like we can other NixOS deployment
tools). That means that we can do something like this:</p>
<pre class="nix"><code>{ inputs.nixpkgs.url = "github:NixOS/nixpkgs/22.11";
outputs = { nixpkgs, ... }: {
nixosConfigurations = {
machine1 = nixpkgs.lib.nixosSystem { … };
machine2 = nixpkgs.lib.nixosSystem { … };
…
};
};
}</code></pre>
<p>… and then we can select which system to build with the desired flake
URI (e.g. <code>.#machine1</code> or <code>.#machine2</code> in the
above example).</p>
<p>Moreover, by virtue of using flakes we can obtain our NixOS
configuration from somewhere other than the current working directory.
For example, you can specify a flake URI like
<code>github:${OWNER}/${REPO}#${ATTRIBUTE}</code> to deploy a NixOS
configuration hosted on GitHub without having to locally clone the
repository. Pretty neat!</p>
<h4 id="conclusion">Conclusion</h4>
<p>I’m not the first person to suggest this trick. In fact, while
researching prior art I stumbled across <a
href="https://discourse.nixos.org/t/morph-nix-based-deployment-tool/1276/2">this
comment from Luke Clifton</a> proposing the same idea of using
<code>nixos-rebuild</code> as a deploy tool. However, other than that
stray comment I couldn’t find any other mentions of this so I figured it
was worth formalizing this trick in a blog post that people could more
easily share.</p>
<p>This post supersedes a <a
href="https://www.haskellforall.com/2018/08/nixos-in-production.html">prior
post of mine</a> where I explained how to deploy a NixOS system using
more low-level idioms (e.g. <code>nix build</code>,
<code>nix copy</code>). Now that <code>nixos-rebuild</code> supports
both flakes and remote systems there’s no real reason to do it the
low-level way.</p>
<p>Edit: An earlier version of this post suggested using
<code>_NIXOS_REBUILD_REEXEC=1</code> to prevent
<code>nixos-rebuild</code> for building itself for the target platform
but then <a href="https://github.com/ncfavier">Naïm Favier</a> pointed
out that you can use the <code>--fast</code> flag instead, which has the
same effect.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com9tag:blogger.com,1999:blog-1777990983847811806.post-55725856437211773092022-12-30T06:32:00.005-08:002022-12-30T06:47:36.165-08:00Nixpkgs support for Linux builders running on macOS<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:creator" content="@GabriellaG439" />
<meta name="twitter:title" content="Nixpkgs support for Linux builders running on macOS" />
<meta name="twitter:description" content="Blog post contextualizing recent work to upstream Linux builders for macOS." />
<title>macos-builder</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>I recently upstreamed a derivation for a Linux builder into Nixpkgs
that’s easy to deploy on macOS. The significance of this work is that
you can now run the following command on macOS:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix run nixpkgs#darwin.builder</span></code></pre></div>
<p>… and that will launch a Linux builder that you can delegate builds
to. For full details, read the <a
href="https://nixos.org/manual/nixpkgs/unstable/#sec-darwin-builder">corresponding
section of the Nixpkgs manual</a>.</p>
<p>In this post, I wanted to provide some of the background and
motivation for this work to help contextualize it.</p>
<h4 id="background---nixos-qemu-vms-on-macos">Background - NixOS
<code>qemu</code> VMs on MacOS</h4>
<p>I wasn’t originally trying to create a Linux builder for macOS when I
began this project. I was actually working on making it as easy as
possible to experiment interactively with (non-builder) NixOS
<code>qemu</code> VMs on macOS.</p>
<p>While searching for prior art related to this I stumbled across the
following Nixpkgs issue requesting exactly this same feature: <a
href="https://github.com/NixOS/nixpkgs/issues/108984">Allowing NixOS
VM’s to be run on macOS</a>.</p>
<p>Even better, by the time I discovered that issue several people had
already done most of the work, culminating in the following repository
demonstrating how all of the features were supposed to fit together: <a
href="https://github.com/YorikSar/nixos-vm-on-macos"><code>YorikSar/nixos-vm-on-macos</code></a>.</p>
<p>In fact, the flake for that repository also came with a binary cache,
so if you just ran:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix run github:YorikSar/nixos-vm-on-macos</span></code></pre></div>
<p>… then you could run the sample NixOS VM from that repository on
macOS without requiring access to an Linux builder because it would
download all the Linux build products from the matching cache. Pretty
neat!</p>
<p>However, this still didn’t completely satisfy my use case for reasons
already <a
href="https://github.com/NixOS/nixpkgs/issues/108984#issuecomment-1207474585">noted
by someone else</a>: it doesn’t work well if you want to run a NixOS VM
that differs even slightly from the included sample VM. Any difference
requires Linux build products to be rebuilt which requires access to a
Linux builder because those build products will not be cached ahead of
time.</p>
<h4 id="background---linuxkit-nix">Background -
<code>linuxkit-nix</code></h4>
<p>The need for a Linux builder wasn’t a showstopper for me because
there was already prior art for bootstrapping a Linux builder on macOS,
which was the <a
href="https://github.com/nix-community/linuxkit-nix"><code>linuxkit-nix</code></a>
project. So what I could have done was:</p>
<ul>
<li>Launch a (non-NixOS) <code>linuxkit</code> VM on macOS for use as a
Linux builder</li>
<li>Use the <code>linuxkit</code> builder to build the desired NixOS
<code>qemu</code> VM</li>
<li>Run that NixOS <code>qemu</code> VM on macOS</li>
</ul>
<p>However, I was curious if I could use a NixOS VM for the first step,
too! In other words:</p>
<ul>
<li>Launch a cached NixOS <code>qemu</code> VM on macOS for use as a
Linux builder</li>
<li>Use the <code>qemu</code> builder to build the desired (non-builder)
NixOS <code>qemu</code> VM</li>
<li>Run that NixOS <code>qemu</code> VM on macOS</li>
</ul>
<p>The only difference between the two approaches is the first step:
instead of using <code>linuxkit</code> to create the Linux builder we
use <code>qemu</code> to create a NixOS builder. This works because the
<code>qemu</code> builder’s NixOS configuration doesn’t need to change
so <a href="https://hydra.nixos.org/">hydra.nixos.org</a> can build and
cache the NixOS <code>qemu</code> builder ahead of time.</p>
<p>There were a few reasons I took interest in this approach:</p>
<ul>
<li><p><code>linuxkit-nix</code> appears to not work on
<code>aarch64-darwin</code> (i.e. Apple Silicon)</p>
<p>This seems like it is <a
href="https://github.com/linuxkit/linuxkit/issues/3626">potentially
fixable</a>, but I wasn’t yet ready to volunteer to do that
work.</p></li>
<li><p>It’s easier to customize a NixOS builder</p>
<p><code>linuxkit-nix</code> doesn’t use NixOS for the builder and
instead <a
href="https://github.com/nix-community/linuxkit-nix/blob/master/linuxkit-builder/default.nix">creates
a bespoke builder</a> for this purpose. This means that you can’t use
the NixOS module system to more easily customize the behavior of the
builder.</p></li>
<li><p>The <code>qemu</code>-based solution is simpler than
<code>linuxkit-nix</code></p>
<p>I think the easiest way to explain this is for me to link to the <a
href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/macos-builder.nix"><code>macos-builder.nix</code>
NixOS module</a>, which has the entirety of the code that I contributed,
which is significantly simpler than <code>linuxkit-nix</code>.</p>
<p>The main reason that the <code>qemu</code>-based solution is simpler
than <code>linuxkit-nix</code> is because it is reusing more
infrastructure that has already been upstreamed into Nixpkgs (most
notably, NixOS and <code>qemu</code> VMs).</p></li>
<li><p><code>linuxkit-nix</code> appears to be unmaintained</p>
<p>There was a nascent attempt to <a
href="https://github.com/NixOS/nixpkgs/pull/29628">upstream
<code>linuxkit-nix</code> into Nixpkgs</a>, but that stalled because it
seems like <code>linuxkit-nix</code> appears to have been abandoned a
couple of years ago.</p>
<p>I could have restored that effort, but personally I was fine with
using the simpler <code>qemu</code>-based approach. I haven’t given up
on the idea of reviving <code>linuxkit-nix</code>, but it’s not on my
immediate roadmap.</p></li>
</ul>
<p>There is one notable downside to using <code>qemu</code> over
<code>linuxkit</code>, which is that <strong><code>qemu</code> is
supposed to be slower than <code>linuxkit</code></strong></p>
<blockquote>
<p>Note: I have not actually verified this claim since I can’t run
<code>linuxkit-nix</code> on my M1 Mac, but this is purportedly the
reason that the authors of <code>linuxkit-nix</code> did not opt to use
<code>qemu</code> for their approach according to <a
href="https://github.com/NixOS/nixpkgs/pull/29628">this PR
description</a>.</p>
</blockquote>
<p><code>qemu</code> performance hasn’t been an issue for me (yet), but
that could change, especially if I try to make use of this at work,
where performance could potentially matter more.</p>
<h4 id="motivation">Motivation</h4>
<p>As I mentioned above, the long-term goal for all of this is to run
NixOS VMs on macOS. There are two main reasons I’m interested in
this:</p>
<ul>
<li><p>I’m working on a <a
href="https://leanpub.com/nixos-in-production">NixOS book</a></p>
<p>… and I wanted macOS users to be able to test-drive example NixOS
configurations on their local machine without requiring them to own and
operate a separate Linux machine.</p></li>
<li><p>I’m interested in running NixOS tests on macOS</p>
<p>… primarily for work-related reasons. At work developers have to
install <code>postgres</code> on their development machines for
integration testing, and it would be much nicer if we could restructure
our integration tests as <a
href="https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests">NixOS
tests</a> (which run inside of <code>qemu</code> VMs instead of running
on the host).</p>
<p>However, at the time of this writing this would still require
additional work which is in progress on this <a
href="https://github.com/NixOS/nixpkgs/pull/193336">draft pull
request</a>.</p></li>
</ul>
</body>
</html>Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com1tag:blogger.com,1999:blog-1777990983847811806.post-46400358080607093512022-12-19T07:34:00.000-08:002022-12-19T07:34:00.433-08:00Nixpkgs support for incremental Haskell builds<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="Nixpkgs support for incremental Haskell builds">
<meta name="twitter:description" content="A post explaining the design behind a new Nixpkgs feature for building Haskell packages incrementally">
<title>incremental</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>The context for this post is that at work I recently implemented Nix
ecosystem support for “incrementally” building Haskell packages. By
“incrementally” I mean that these Nix builds only need to build what
changed since the last full build of the package so that the package
doesn’t need to be built from scratch every time.</p>
<p>The pull requests implementing this feature have not yet been
approved or merged at the time of this writing, but I figured that I
would explain the motivation, design, results, and limitations of this
work to hopefully persuade people that this work should be merged.</p>
<p>If you’re not interested in the design then you can skip straight to
the <a href="#demo">Demo</a> section below.</p>
<h4 id="background">Background</h4>
<p>I work on <a href="https://mercury.com/">Mercury</a>’s Backend
Development User Experience team and we support developers contributing
to a large Haskell monolith consisting of 3000+ modules. That may seem
like a lot but the vast majority of these modules are small and the
whole codebase takes ~14 minutes to compile in CI if we disable
optimizations (although we still build with optimizations enabled for
deployment).</p>
<p>In my experience, that’s pretty good for a Haskell project of this
size, thanks not only to the work of our team but also other teams who
also contribute to improving the development experience. In fact, the
pioneering work for this “incremental builds” feature actually
originated from two engineers outside our team.</p>
<p>First, <a href="https://github.com/hdgarrood">Harry Garrood</a>
improved GHC’s change detection algorithm so that GHC would use the hash
of the file to detect changes instead of using the timestamp. In <a
href="https://harry.garrood.me/blog/easy-incremental-haskell-ci-builds-with-ghc-9.4/">this
post</a> he explains how you can make use of this to implement
incremental builds for traditional CI services (e.g. GitHub actions)
where each build reuses the intermediate build products from the prior
build instead of building from scratch.</p>
<p>That alone would not be enough for us to use this at work since we
use Nix where this sort of build impurity doesn’t fly. However, Harry
and <a href="https://github.com/lf-">Jade Lovelace</a> prototyped using
this feature in Nixpkgs so that Nix builds of Haskell packages could
also reuse intermediate build products from prior builds to save work.
You can <a
href="https://github.com/hdgarrood/haskell-incremental-nix-example">find
their prototype here</a>.</p>
<p>The basic idea behind the prototype Nixpkgs integration is that you
split a Haskell package build into two separate builds:</p>
<ul>
<li><p>A “full build” that builds the Haskell package from scratch</p>
<p>This full build exports its intermediate build products (i.e. the
<code>dist</code> directory) which can then be reused by:</p></li>
<li><p>An “incremental build” that only builds what changed since the
full build</p>
<p>This incremental build imports the intermediate build products from
the corresponding full build so that it doesn’t have to build the
package from scratch.</p></li>
</ul>
<p>So you might wonder: if that was already implemented then what work
still remained for me to do?</p>
<h4 id="problem">Problem</h4>
<p>The main issue with the initial Nixpkgs integration is that it does
not provide any support for selecting which Git revision to use as the
basis for the full build. The existing solutions require some
out-of-band process to automatically select and lock the appropriate git
revision to use for the older (full) build.</p>
<h5 id="non-solution-0-rolling-rebuilds">Non-solution #0: Rolling
rebuilds</h5>
<p>The first non-solution is for each revision to always reuse the build
products from the previous revision. This doesn’t work well with Nix
because it would create an increasingly-long chain of dependent
derivations; in order to build the most recent revision you’d have to
build all preceding revisions.</p>
<p>The dilemma here is that Nix is forcing us to confront something that
other build tools gloss over: if you’re always reusing build products
from the last build then you can’t accurately reproduce the most recent
build from scratch without reproducing all prior builds. You’ve
essentially “contaminated” the current build with all prior builds by
doing things in this way.</p>
<p>So what we really want is something more like this:</p>
<blockquote>
<p>Periodically do a full build from scratch and then make each
incremental build relative to the last full rebuild.</p>
</blockquote>
<p>That’s much more compatible with Nix because then we only need to do
two builds of our project if we rebuild things from scratch, instead of
one build for every revision in our project’s history.</p>
<blockquote>
<p>There’s also another issue with rolling rebuilds when you’re not
using Nix, which is that most naïve attempts to do this don’t ensure
that the starting build products came from the parent commit. You can
end up with contamination of build products across branches if you’re
not careful, which further complicates reproducibility.</p>
</blockquote>
<h5 id="non-solution-1-lockfile">Non-solution #1: Lockfile</h5>
<p>Okay, so suppose you periodically do a full build of the project from
scratch and then each incremental build is relative to the last full
build. You would need to do a full rebuild frequently enough so that the
incremental builds stay quick. If you wait too long in between full
rebuilds then the project will evolve to the point where the incremental
builds can no longer reuse most of the build products from the last full
build and in the extreme case the incremental builds degenerate into
full builds if they can’t reuse any old build products.</p>
<p>For example, at our work we currently do a full build of our large
package once a day, so we need some way to update the full build to
point to the last revision from the preceding day.</p>
<p><a
href="https://felixspringer.xyz/homepage/blog/incrementalHaskellBuildsWithNix">One
existing approach</a> to solving this involved using Nix flakes to
manage the git revision for the older build. The idea is that you
periodically run <code>nix flake update</code> to update the revision
used for the full build and you might even automate this process by
having some recurring <code>cron</code> job generate a pull request or
commit to bump this revision on the main development branch. You don’t
have to use flakes for this purpose, but flakes are probably the most
ergonomic solution along these lines.</p>
<p>However, there are a few issues with this approach:</p>
<ul>
<li><p>It only works well for short-lived pull requests</p>
<p>In other words, if you update the revision used for the full build
once a day then typically only pull requests that are less than a day
old will benefit from incremental builds.</p>
<p>Specifically, what we’d really like is “branch-local” incremental
builds. In other words if a longer-lived development branch were to
deposit a few commits a day we’d like there to be a full rebuild once a
day on that branch so that incremental builds against the tip of that
development branch remain snappy.</p></li>
<li><p>It pollutes the <code>git</code> history</p>
<p>If you bump the lockfile, say, once per day then that’s one junk
commit that you’ve added to your <code>git</code> history every
day.</p></li>
<li><p>It’s difficult to open source any useful automation around
this</p>
<p>If the solution requires out-of-band machinery (e.g. some recurring
<code>cron</code> job) to bump the lockfile you can’t provide a great
user experience for open source projects. It only really works well for
proprietary projects that can tolerate that complexity.</p></li>
</ul>
<p>That last point was the most important one for me. Generally, when I
design something (even something intended for internal, proprietary use)
I try to design it in such a way that it works well in an open source
context, too. In my experience, doing things in this way tends to
improve the design, quality, and user experience of software that I
build.</p>
<p>In particular, I wanted a solution where all the automation could be
implemented entirely within the Nix language. However, this is not
possible in Nix’s present form!</p>
<h5 id="non-solution-2-rollback-derivation">Non-solution #2: Rollback
derivation</h5>
<p>So what I really wanted was a Nix function (which I will call
“<code>truncate</code>”) that would take any <code>git</code> repository
and roll it back in time to the last commit before some repeating time
boundary (where the time boundary might be, say, an hour, or day, or
week). For simplicity, let’s just say that the desired time interval is
one day so I want to roll back the repository to the last revision from
the day before.</p>
<p>If I had such a <code>truncate</code> function then it would be easy
to automatically select which revision to use for the full build. I
would:</p>
<ul>
<li><p>extract the source <code>git</code> repository from the current
Haskell package build</p></li>
<li><p><code>truncate</code> that <code>git</code> repository to the
last revision from the day before</p></li>
<li><p>Use that “truncated” revision as the source for the full
build</p></li>
<li><p>Use that full build as the input to the current (incremental)
build</p></li>
</ul>
<p>Then if I built multiple revisions for the same day they would all
share the same full build since they would all get “truncated” to the
same revision from the previous day.</p>
<p>However, there isn’t a great way to implement this
<code>truncate</code> function in Nix. To see why, consider the
following (wrong) solution:</p>
<ul>
<li><p>extract the source <code>git</code> repository from the current
Haskell package build</p>
<p>Let’s call the derivation for this <code>git</code> repository
“<code>src</code>”</p></li>
<li><p>create a new Nix derivation (“<code>src2</code>”) that rolls back
<code>src</code></p>
<p>In other words, this would be a trivial Nix derivation that begins
from <code>src</code> and runs something like:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git checkout <span class="va">$(</span><span class="fu">git</span> rev-list <span class="at">-1</span> <span class="at">--before</span> <span class="st">'1 day ago'</span> HEAD<span class="va">)</span></span></code></pre></div>
<p>… and stores that as the result</p></li>
<li><p>Use <code>src2</code> as the input to the full build</p></li>
</ul>
<p>Do you see the problem with that approach?</p>
<p>The above wrong solution doesn’t allow multiple incremental builds
from the same day to share the same full build from the prior day. This
is because <code>src2</code> depends on <code>src</code> and since each
incremental build has a different <code>src</code> repository then each
also have a different <code>src2</code> derivation and therefore a
different full build. That in turn defeats the purpose of incremental
builds if we have to do a new full rebuild for each incremental
build.</p>
<p>For this to work we would need a way to roll back a <code>git</code>
repository to an older revision that less sensitive to the current
revision.</p>
<h5 id="non-solution-3-plain-fetchgit">Non-solution #3: Plain
<code>fetchGit</code></h5>
<p>The <code>builtins.fetchGit</code> utility <em>almost</em> does what
we want! This primitive function lets you fetch a <code>git</code>
repository at evaluation time, like this:</p>
<pre><code>nix-repl> builtins.fetchGit { url = ~/proj/turtle; revision = "837f52d2101368bc075d382774460a717904d2ab"; }
{ lastModified = 1655501878; lastModifiedDate = "20220617213758"; narHash = "sha256-Ic4N2gzm0hYsPCynkzETJv7lpAWO1KM+FO+r3ov60y0="; outPath = "/nix/store/ygznanxv6rmbxw5gkgk7axfxazhsa93z-source"; rev = "837f52d2101368bc075d382774460a717904d2ab"; revCount = 566; shortRev = "837f52d"; submodules = false; }</code></pre>
<p>The above result is the same no matter what revision I currently have
checked out at <code>~/proj/turtle</code> because Nix’s
<code>fetchGit</code> function produces a content-addressed derivation.
In other words, if two invocations of <code>fetchGit</code> generate the
same final repository state then they share the same
<code>outPath</code>. This is exactly the behavior we want: we need the
source repository for the full build to be content-addressed so that
multiple incremental builds can share the same full build.</p>
<p>However, the problem is that I <em>don’t</em> exactly know which
revision I want. What I <em>really</em> want to be able to say is “get
me the last revision from the day before this other revision”.
<code>fetchGit</code> does not expose any way to do something like
that.</p>
<p>That brings us to the actual solution:</p>
<h5 id="solution">Solution</h5>
<p>The solution I went with was the following two pull requests:</p>
<ul>
<li><p><a href="https://github.com/NixOS/nix/pull/7362">Add optional
<code>date</code> argument to <code>builtins.fetchGit</code></a></p>
<p>This amends <code>builtins.fetchGit</code> to allow a date
specification, which can either be a relative date
(e.g. <code>1 day ago</code>) or an absolute date (e.g.
<code>2020-01-01T00:00:00</code> or a Unix timestamp like
<code>1671388622</code>). Basically, this argument accepts anything
<code>git</code> accepts as a date specification (which is a lot since
<code>git</code> is pretty flexible in this regard).</p>
<p>The cool thing about this change is that it doesn’t compromise the
purity of <code>builtins.fetchGit</code>. If a given
<code>fetchGit</code> specification was pure then adding a date
specification preserves that purity.</p></li>
<li><p><a href="https://github.com/NixOS/nixpkgs/pull/204020">Add
<code>haskell.lib.incremental</code> utility</a></p>
<p>This pull request actually does two separate things:</p>
<ul>
<li><p>This polishes and upstreams the prototype support for incremental
builds</p>
<p>In other words, this upstreams Harry and Jade’s work to split a
Haskell build into two builds: a full build and incremental
build</p></li>
<li><p>This uses the <code>fetchGit</code> patch to automate the full
build selection</p>
<p>There’s a new <code>pkgs.haskell.lib.incremental</code> utility which
uses <code>builtins.fetchGit</code> to automatically update the full
build for you and it has all the desired behaviors (including
branch-local incrementalism).</p></li>
</ul>
<p>I could have split this into two separate pull request (and I still
might) but for internal testing purposes it was easier to do everything
on one branch. I’m waiting for a decision on the other pull request
before deciding whether or not to split up this branch.</p></li>
</ul>
<h4 id="demo">Demo</h4>
<p>I’ll use my <code>turtle</code> package as the running example for
the demo. If you clone the <code>gabriella/incremental</code> branch of
my <code>turtle</code> repository:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git clone <span class="at">--branch</span> gabriella/incremental <span class="dt">\</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> https://github.com/Gabriella439/turtle.git</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cd turtle</span></code></pre></div>
<p>… you’ll find the following <code>default.nix</code> file making use
of the Nixpkgs support for incremental Haskell builds:</p>
<pre class="nix"><code>{ interval ? 24 * 60 * 60 }:
let
nixpkgs = builtins.fetchTarball {
url = "https://github.com/MercuryTechnologies/nixpkgs/archive/696e0820b03e8ea7ad6a9ba21a00a79c91efc580.tar.gz";
sha256 = "1k3swii3absl154154lmk6zjw11vzzqx8skaiw1250armgfyv9v8";
};
# We need GHC 9.4 or newer for this feature to work
compiler ="ghc94";
overlay = self: super: {
haskell = super.haskell // {
packages = super.haskell.packages // {
"${compiler}" =
super.haskell.packages."${compiler}".override (old: {
overrides =
self.lib.fold
self.lib.composeExtensions
(old.overrides or (_: _: { }))
[ (self.haskell.lib.packageSourceOverrides {
turtle = ./.;
})
(hself: hsuper: {
turtle-incremental =
self.haskell.lib.compose.incremental
{ inherit interval;
makePreviousBuild =
truncate: (import (truncate ./.) { }).turtle;
}
hsuper.turtle;
})
];
});
};
};
};
pkgs = import nixpkgs { config = { }; overlays = [ overlay ]; };
in
{ inherit (pkgs.haskell.packages."${compiler}")
turtle
turtle-incremental
;
}</code></pre>
<p>However, that alone is not enough to make use of incremental builds.
If you attempt to build that (at the time of this writing) you’ll get an
error message like this:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build <span class="at">--file</span> ./default.nix turtle-incremental</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">error:</span> evaluation aborted with the following error message:</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="st">'pkgs.haskell.lib.incremental requires Nix version 2.12.0pre20221128_32c182b or</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="st">newer'</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="kw">(</span><span class="ex">use</span> <span class="st">'--show-trace'</span> to show detailed location information<span class="kw">)</span></span></code></pre></div>
<p>The Nixpkgs support for incremental builds depends on a matching
change to the Nix interpreter, so you actually have to run:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix run github:Gabriella439/nix/gabriella/fetchGit <span class="at">--</span> <span class="dt">\</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a> build <span class="at">--file</span> ./default.nix turtle-incremental</span></code></pre></div>
<p>… or if you don’t yet have flakes enabled, then use this pedantically
complete command:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix <span class="at">--option</span> extra-experimental-features <span class="st">'nix-command flakes'</span> <span class="dt">\</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> run github:Gabriella439/nix/gabriella/fetchGit <span class="at">--</span> <span class="dt">\</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> build <span class="at">--file</span> ./default.nix turtle-incremental</span></code></pre></div>
<p>… and that will definitely work.</p>
<p>Once the build is complete you can inspect the logs and you should
see something like the following <code>buildPhase</code>:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix log ./result</span></code></pre></div>
<pre><code>…
@nix { "action": "setPhase", "phase": "buildPhase" }
building
Preprocessing library for turtle-1.6.1..
Building library for turtle-1.6.1..
Preprocessing test suite 'regression-broken-pipe' for turtle-1.6.1..
Building test suite 'regression-broken-pipe' for turtle-1.6.1..
[2 of 2] Linking dist/build/regression-broken-pipe/regression-broken-pipe [Libr>
Preprocessing test suite 'regression-masking-exception' for turtle-1.6.1..
Building test suite 'regression-masking-exception' for turtle-1.6.1..
[2 of 2] Linking dist/build/regression-masking-exception/regression-masking-exc>
Preprocessing test suite 'tests' for turtle-1.6.1..
Building test suite 'tests' for turtle-1.6.1..
[2 of 2] Linking dist/build/tests/tests [Library changed]
Preprocessing test suite 'system-filepath-tests' for turtle-1.6.1..
Building test suite 'system-filepath-tests' for turtle-1.6.1..
[2 of 2] Linking dist/build/system-filepath-tests/system-filepath-tests [Librar>
Preprocessing test suite 'cptree' for turtle-1.6.1..
Building test suite 'cptree' for turtle-1.6.1..
[2 of 2] Linking dist/build/cptree/cptree [Library changed]
…</code></pre>
<p>This is shows that the incremental builds are indeed working. We
still have to re-link some executables (for reasons that are still not
clear to me), but none of the Haskell modules needed to be rebuilt since
nothing has changed (yet) since the last rebuild.</p>
<p>Now let’s test that by making a small whitespace change to one of the
<code>Turtle</code> modules:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="op">>></span> src/Turtle/Prelude.hs </span></code></pre></div>
<p>Then if we rebuild the package we’ll see the following build
phase:</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix <span class="at">--option</span> extra-experimental-features <span class="st">'nix-command flakes'</span> <span class="dt">\</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> run github:Gabriella439/nix/gabriella/fetchGit <span class="at">--</span> <span class="dt">\</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a> build <span class="at">--file</span> ./default.nix <span class="at">--print-build-logs</span></span></code></pre></div>
<pre><code>…
turtle> building
turtle> Preprocessing library for turtle-1.6.1..
turtle> Building library for turtle-1.6.1..
turtle> [ 7 of 10] Compiling Turtle.Prelude ( src/Turtle/Prelude.hs, dist/build/Turtle/Prelude.o, dist/build/Turtle/Prelude.dyn_o ) [Source file changed]
turtle> src/Turtle/Prelude.hs:319:1: warning: [-Wunused-imports]
turtle> The import of ‘Data.Monoid’ is redundant
turtle> except perhaps to import instances from ‘Data.Monoid’
turtle> To import instances alone, use: import Data.Monoid()
turtle> |
turtle> 319 | import Data.Monoid ((<>))
turtle> | ^^^^^^^^^^^^^^^^^^^^^^^^^
turtle> Preprocessing test suite 'regression-broken-pipe' for turtle-1.6.1..
turtle> Building test suite 'regression-broken-pipe' for turtle-1.6.1..
turtle> [2 of 2] Linking dist/build/regression-broken-pipe/regression-broken-pipe [Library changed]
turtle> Preprocessing test suite 'regression-masking-exception' for turtle-1.6.1..
turtle> Building test suite 'regression-masking-exception' for turtle-1.6.1..
turtle> [2 of 2] Linking dist/build/regression-masking-exception/regression-masking-exception [Library changed]
turtle> Preprocessing test suite 'tests' for turtle-1.6.1..
turtle> Building test suite 'tests' for turtle-1.6.1..
turtle> [2 of 2] Linking dist/build/tests/tests [Library changed]
turtle> Preprocessing test suite 'system-filepath-tests' for turtle-1.6.1..
turtle> Building test suite 'system-filepath-tests' for turtle-1.6.1..
turtle> [2 of 2] Linking dist/build/system-filepath-tests/system-filepath-tests [Library changed]
turtle> Preprocessing test suite 'cptree' for turtle-1.6.1..
turtle> Building test suite 'cptree' for turtle-1.6.1..
turtle> [2 of 2] Linking dist/build/cptree/cptree [Library changed]
…</code></pre>
<p>Our package only built the “diff” (the <code>Turtle.Prelude</code>
module we just changed)!</p>
<h4 id="benchmarks">Benchmarks</h4>
<p>For the <code>turtle</code> package the speed-up is not a huge deal
because the package doesn’t take long time to compile, but the benefit
for our main project at work is dramatic!</p>
<p>As I mentioned in the introduction, our work project normally takes
~14 minutes to build and after this change builds can be as fast as ~3.5
minutes. In fact, they could even be faster except for the presence of a
<code>Paths_*</code> module that is rebuilt each time and triggers a
large number of gratuitous downstream rebuilds (we’re working on fixing
that).</p>
<h4 id="limitations">Limitations</h4>
<p>There is one major issue with this work, which is that it <em>does
not work well with flakes</em>.</p>
<p>Specifically, if you try to turn the above <code>default.nix</code>
into the equivalent flake the build will fail because Nix’s flake
mechanism will copy the project into the <code>/nix/store</code> but
without the <code>.git</code> history, so <code>builtins.fetchGit</code>
will fail to to fetch the current repository’s history necessary to
truncate the build to the previous day.</p>
<p>I believe this can be fixed with a change to flakes to support
something like a <code>?shallow=false</code> or
<code>?allRefs=true</code> addendum to <code>git</code> URLs, but I have
not implemented that, yet.</p>
</body>
</html>Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com6tag:blogger.com,1999:blog-1777990983847811806.post-70962930965719199332022-10-24T05:26:00.003-07:002022-10-24T05:26:45.820-07:00How to correctly cache build-time dependencies using Nix<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:creator" content="@GabriellaG439" />
<meta name="twitter:title" content="How to correctly cache build-time dependencies using Nix" />
<meta name="twitter:description" content="A guide to uploading Nix dependencies to a cache efficiently and reliably" />
<title>caching</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>Professional Nix users often create a shared cache of Nix build
products so that they can reuse build products created by continuous
integration (CI). For example, CI might build Nix products for each main
development branch of their project or even for every pull request and
it would be nice if those build products could be shared with all
developers via a cache.</p>
<p>However, uploading build products to a cache is a little non-trivial
if you don’t already know the “best” solution, which is the subject of
this post.</p>
<p>The solution described in this post is:</p>
<ul>
<li><p>Simple</p>
<p>It only takes a few lines of Bash code because we use the Nix
command-line interface idiomatically</p></li>
<li><p>Efficient</p>
<p>It is very cheap to compute which build products to upload and
requires no additional builds nor an exorbitant amount of disk
space</p></li>
<li><p>Accurate</p>
<p>It uploads the build products that most people would intuitively want
to upload</p></li>
</ul>
<blockquote>
<p>Note: Throughout this post I will be using the newer Nix command-line
interface and flakes, which requires either adding this line to your
<code>nix.conf</code> file:</p>
<pre><code>extra-experimental-features = nix-command flakes</code></pre>
<p>… and restarting your Nix daemon (if you have a multi-user Nix
installation), or alternatively adding these flags to the beginning of
all <code>nix</code> commands throughout this post:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix <span class="at">--option</span> extra-experimental-features <span class="st">'nix-command flakes'</span> …</span></code></pre></div>
</blockquote>
<h4 id="wrong-solution-0">Wrong solution #0</h4>
<p>As a running example, suppose that our CI builds a top-level build
product using a command like this:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build .#example</span></code></pre></div>
<p>The naïve way to upload that to the cache would be:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix store sign <span class="at">--key-file</span> <span class="st">"</span><span class="va">${KEY_FILE}</span><span class="st">"</span> <span class="at">--recursive</span> .#example</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix copy <span class="at">--to</span> s3://cache.example.com .#example</span></code></pre></div>
<blockquote>
<p>Note: You will need to generate a <code>KEY_FILE</code> using the
<code>nix-store --generate-binary-cache-key</code> command if you
haven’t already. For more details, see the following documentation from
the manual:</p>
<details>
<summary>
Click to expand to see the documentation
</summary>
<pre><code>Operation --generate-binary-cache-key
Synopsis
nix-store --generate-binary-cache-key key-name secret-key-file
public-key-file
Description
This command generates an Ed25519 key pair (http://ed25519.cr.yp.to/)
that can be used to create a signed binary cache. It takes three
mandatory parameters:
1. A key name, such as cache.example.org-1, that is used to look up
keys on the client when it verifies signatures. It can be
anything, but it’s suggested to use the host name of your cache
(e.g. cache.example.org) with a suffix denoting the number of the
key (to be incremented every time you need to revoke a key).
2. The file name where the secret key is to be stored.
3. The file name where the public key is to be stored.</code></pre>
</details>
</blockquote>
<p>That seems like a perfectly reasonable thing to do, right? However,
the problem with that is that it is <em>incomplete</em>, meaning that
the cache would still be missing several useful build products that
developers would expect to be there.</p>
<p>Specifically, the above command only copies the “run-time”
dependencies of our build product whereas most developers expect the
cache to also include “build-time” dependencies, and I’ll explain the
distinction between the two.</p>
<h4 id="run-time-vs.-build-time">Run-time vs. Build-time</h4>
<p>Many paths in the <code>/nix/store</code> are not “valid” in
isolation. They typically depend on other paths within the
<code>/nix/store</code>.</p>
<p>For example, suppose that I build the GNU <code>hello</code> package,
like this:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="op">$</span> nix build nixpkgs<span class="op">#</span>hello</span></code></pre></div>
<p>I can query all of the other paths within the <code>/nix/store</code>
that the <code>hello</code> package transitively depends on <em>at
run-time</em> using this command:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-store <span class="at">--query</span> <span class="at">--requisites</span> ./result</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/jzid7pfrhv6gpiwqbx6763v0g9c3bdzb-libobjc-11.0.0</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/9rb5qaba71mkgfgd8wfqg03cmi46xarg-apple-framework-CoreFoundation-11.0</span><span class="op">></span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/akjp4x41jjx5hzgzrschwqzr8qfsdpys-hello-2.12.1</span></span></code></pre></div>
<p>… or I can print the same information in tree form like this:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-store <span class="at">--query</span> <span class="at">--tree</span> ./result</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/akjp4x41jjx5hzgzrschwqzr8qfsdpys-hello-2.12.1</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ex">└───/nix/store/9rb5qaba71mkgfgd8wfqg03cmi46xarg-apple-framework-CoreFoundation-11.0.0</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">└───/nix/store/jzid7pfrhv6gpiwqbx6763v0g9c3bdzb-libobjc-11.0.0</span></span></code></pre></div>
<p>On my macOS machine, it has two run-time dependencies (other than
itself) within the <code>/nix/store</code>: <code>libobjc</code> and
<code>apple-framework-CoreFoundation-11.0</code>.</p>
<blockquote>
<p>Note: there might be other run-time dependencies, because I believe
Nixpkgs support for macOS requires some impure system dependencies, but
I’m not an expert on this so I could be wrong.</p>
</blockquote>
<p>These are called “run-time” dependencies because we cannot run our
<code>hello</code> executable without them.</p>
<p>Nix prevents us from getting into situations where a
<code>/nix/store</code> path is missing its run-time dependencies. For
example, if I were to <code>nix copy</code> the <code>hello</code> build
product to any cache, then Nix would perform the following steps, in
order:</p>
<ul>
<li><p>Copy <code>libobjc</code> to the cache</p>
<p>… since that has no dependencies</p></li>
<li><p>Copy <code>apple-framework-CoreFoundation</code> to the cache</p>
<p>… since its <code>libobjc</code> dependency is now satisfied within
the cache</p></li>
<li><p>Copy <code>hello</code> to the cache</p>
<p>… since its <code>apple-framework-CoreFoundation</code> dependency is
now satisfied within the cache</p></li>
</ul>
<p>However, Nix also has a separate notion of “build-time” dependencies,
which are dependencies that we need to in order to build the
<code>hello</code> package.</p>
<blockquote>
<p>Note: The reason we’re interested in build-time dependencies for our
project is that we want developers to be able to rebuild the project if
they make any changes to the source code. If we were to only cache the
run-time dependencies of our project that wouldn’t cache the development
environment that developers need.</p>
</blockquote>
<p>In order to query these dependencies I need to first get the
“derivation” (<code>.drv</code> file) for <code>hello</code>:</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> DERIVATION=<span class="st">"</span><span class="va">$(</span><span class="ex">nix</span> path-info <span class="at">--derivation</span> nixpkgs#hello<span class="va">)</span><span class="st">"</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> declare <span class="at">-p</span> DERIVATION</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="bu">typeset</span> <span class="va">DERIVATION</span><span class="op">=</span>/nix/store/4a78f0s4p5h2sbcrrzayl5xas2i7zq1m-hello-2.12.1.drv</span></code></pre></div>
<p>You can think of a derivation file as a build recipe that contains
instructions for how to build the corresponding build product (the
<code>hello</code> package in this case).</p>
<p>I can query the direct dependencies of that derivation using this
command:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-store <span class="at">--query</span> <span class="at">--references</span> <span class="st">"</span><span class="va">${DERIVATION}</span><span class="st">"</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/labgzlb16svs1z7z9a6f49b5zi8hb11s-bash-5.1-p16.drv</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/cdk3pz11mvhqpphr0197wwmzhqppn7rl-stdenv-darwin.drv</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/hwymznwkd1kgf5ldcldjl9bnc1wz2azb-hello-2.12.1.tar.gz.drv</span></span></code></pre></div>
<p>Many of these dependencies are themselves derivations
(<code>.drv</code> files), meaning that they represent other packages
that Nix might have to build or fetch from a cache.</p>
<blockquote>
<p>Note: the <code>.drv</code> files are actually not the build-time
dependencies, but rather the instructions for building them. You can
convert any <code>.drv</code> file to the matching product it is
supposed to build using the same <code>nix build</code> command, like
this:</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build /nix/store/labgzlb16svs1z7z9a6f49b5zi8hb11s-bash-5.1-p16.drv</span></code></pre></div>
</blockquote>
<p>Does that mean that these build-time dependencies are on our machine
if we built <code>nixpkgs#hello</code>? Not necessarily. In fact, in all
likelihood the <code>nixpkgs#hello</code> build was cached, meaning that
<code>nix build nixpkgs#hello</code> only downloaded <code>hello</code>
and its run-time dependencies and no build-time dependencies were
required nor installed by Nix.</p>
<p>However, I could in principle force Nix to build the
<code>hello</code> package instead of downloading it from a cache, like
this:</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build nixpkgs#hello <span class="at">--rebuild</span></span></code></pre></div>
<p>… and that would download the direct build-time dependencies of the
<code>hello</code> package in order to rebuild the package.</p>
<h4 id="wrong-solution-1">Wrong solution #1</h4>
<p>By this point you might suppose that you have enough information to
come up with a better set of <code>/nix/store</code> paths to cache.
Your solution might look like this:</p>
<ul>
<li><p>Get the derivation for the top-level build product</p></li>
<li><p>Get the direct build-time dependencies of that
derivation</p></li>
<li><p>Build the top-level build product and its direct build-time
dependencies</p></li>
<li><p>Cache the top-level build product and its direct build-time
dependencies</p></li>
</ul>
<p>In other words, something like this Nix code:</p>
<div class="sourceCode" id="cb13"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> DERIVATION=<span class="st">"</span><span class="va">$(</span><span class="ex">nix</span> path-info <span class="at">--derivation</span> <span class="st">"</span><span class="va">${BUILD}</span><span class="st">"</span><span class="va">)</span><span class="st">"</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> DEPENDENCIES=<span class="er">(</span><span class="va">$(</span><span class="ex">nix-store</span> <span class="at">--query</span> <span class="at">--references</span> <span class="st">"</span><span class="va">${DERIVATION}</span><span class="st">"</span><span class="va">)</span><span class="kw">)</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build <span class="st">"</span><span class="va">${BUILD}</span><span class="st">"</span> <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix store sign <span class="at">--key-file</span> <span class="st">"</span><span class="va">${KEY_FILE}</span><span class="st">"</span> <span class="at">--recursive</span> <span class="st">"</span><span class="va">${BUILD}</span><span class="st">"</span> <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix copy <span class="at">--to</span> <span class="st">"</span><span class="va">${CACHE}</span><span class="st">"</span> <span class="st">"</span><span class="va">${BUILD}</span><span class="st">"</span> <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span></span></code></pre></div>
<p>This is better, but still not good enough!</p>
<p>The problem with this solution is that it only works well if your
dependencies never change and you only modify your top-level project. If
you upgrade or patch any of your direct build-time dependencies then you
need to have <em>their</em> build-time dependencies cached so that you
can quickly rebuild them.</p>
<p>In fact, going two layers deep is still not enough; in practice you
can’t easily anticipate in advance how deep in the build-time dependency
tree you might need to patch or upgrade things. For example, you might
need to patch or upgrade your compiler, which is really deep in your
build-time dependency tree.</p>
<h4 id="wrong-solution-2">Wrong solution #2</h4>
<p>Okay, so maybe we can try to build and cache <em>all</em> of our
build-time dependencies?</p>
<p>Wrong again. There are way too many of them. You can query them by
replacing <code>--references</code> with <code>--requisites</code> and
you’ll a giant list of results, even for “small” packages. For
example:</p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> DERIVATION=<span class="va">$(</span><span class="ex">nix</span> path-info <span class="at">--derivation</span> nixpkgs#hello<span class="va">)</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-store <span class="at">--query</span> <span class="at">--requisites</span> <span class="st">"</span><span class="va">${DERIVATION}</span><span class="st">"</span></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/8djp1rizc1dblv8svnb0mpa0c3lwvc17-drop-comments.patch</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh</span></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/3glray2y14jpk1h6i599py7jdn3j2vns-mkdir.drv</span></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/50ql5q0raqkcydmpi6wqvnhs9hpdgg5f-cpio.drv</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/81xahsrhpn9mbaslgi5sz7gsqra747d4-unpack-bootstrap-tools-aarch64.sh</span></span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/fzbk4fnbjqhr0l1scx5fspsx5najbrbm-bootstrap-tools.cpio.bz2.drv</span></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/gxzl4vmccqj89yh7kz62frkxzgdpkxmp-sh.drv</span></span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span> 🌺 500+ derivations later 🌺 …</span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/i0zc5mm4vpj3lviyydb9s73j53mypkrg-nghttp2-1.49.0.drv</span></span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/w37b5s734m53gxnzqyb5v0v98mhdfg2i-coreutils-9.1.drv</span></span>
<span id="cb14-14"><a href="#cb14-14" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/mmsrbggvzn6rwlx1ijw90sw3wvhzj18j-openssl-3.0.5.drv</span></span>
<span id="cb14-15"><a href="#cb14-15" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/n7iibs6b818v402j0yczf4mgy73sbzpv-libssh2-1.10.0.drv</span></span>
<span id="cb14-16"><a href="#cb14-16" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/z074ki54p77r7db3wsgxh9p18f67xnv8-curl-7.85.0.drv</span></span>
<span id="cb14-17"><a href="#cb14-17" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/hwymznwkd1kgf5ldcldjl9bnc1wz2azb-hello-2.12.1.tar.gz.drv</span></span>
<span id="cb14-18"><a href="#cb14-18" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/4a78f0s4p5h2sbcrrzayl5xas2i7zq1m-hello-2.12.1.drv</span></span></code></pre></div>
<details>
<summary>
Click to expand and see the full list of build-time dependencies
</summary>
<pre><code>/nix/store/8djp1rizc1dblv8svnb0mpa0c3lwvc17-drop-comments.patch
/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh
/nix/store/3glray2y14jpk1h6i599py7jdn3j2vns-mkdir.drv
/nix/store/50ql5q0raqkcydmpi6wqvnhs9hpdgg5f-cpio.drv
/nix/store/81xahsrhpn9mbaslgi5sz7gsqra747d4-unpack-bootstrap-tools-aarch64.sh
/nix/store/fzbk4fnbjqhr0l1scx5fspsx5najbrbm-bootstrap-tools.cpio.bz2.drv
/nix/store/gxzl4vmccqj89yh7kz62frkxzgdpkxmp-sh.drv
/nix/store/pjbpvdy0gais8nc4sj3kwpniq8mgkb42-bzip2.drv
/nix/store/7kcayxwk8khycxw1agmcyfm9vpsqpw4s-bootstrap-tools.drv
/nix/store/1i5y55x4b4m9qkx5dqbmr1r6bvrqbanw-multiple-outputs.sh
/nix/store/59jmzisg8fkm9c125fw384dqq1np602l-move-docs.sh
/nix/store/bnj8d7mvbkg3vdb07yz74yhl3g107qq5-patch-shebangs.sh
/nix/store/cickvswrvann041nqxb0rxilc46svw1n-prune-libtool-files.sh
/nix/store/ckzrg0f0bdyx8rf703nc61r3hz5yys9q-builder.sh
/nix/store/fyaryjvghbkpfnsyw97hb3lyb37s1pd6-move-lib64.sh
/nix/store/g8xg0i02aqwhgxwd2vnp5ax3d6lrkg1v-strip.sh
/nix/store/jngr4r80x5jn482ckqrfh08ljrx1k86f-setup.sh
/nix/store/kd4xwxjpjxi71jkm6ka0np72if9rm3y0-move-sbin.sh
/nix/store/kxw6q8v6isaqjm702d71n2421cxamq68-make-symlinks-relative.sh
/nix/store/m54bmrhj6fqz8nds5zcj97w9s9bckc9v-compress-man-pages.sh
/nix/store/ngg1cv31c8c7bcm2n8ww4g06nq7s4zhm-set-source-date-epoch-to-latest.sh
/nix/store/wlwcf1nw2b21m4gghj70hbg1v7x53ld8-reproducible-builds.sh
/nix/store/nbxwxwqwcr9rrmxb6gb532f18102815x-bootstrap-stage0-stdenv-darwin.drv
/nix/store/ycwm35msmsdi2qgjax1slmjffsmwy8am-write-mirror-list.sh
/nix/store/i65va14cylqc74y80ksgnrsaixk39mmh-mirrors-list.drv
/nix/store/lphxcbw5wqsjskipaw1fb8lcf6pm6ri6-builder.sh
/nix/store/bgp77z9z42x35vmwyfywqaiqsmnb3ffa-patchutils-0.3.3.tar.xz.drv
/nix/store/3lhw0v2wyzimzl96xfsk6psfmzh38irh-bash51-007.drv
/nix/store/3p62kw9mpkcp0grhirfn46i9afhqf0c9-bash51-015.drv
/nix/store/3za6mykjk49sr616w80lvmy5xcmbkrp3-bash51-006.drv
/nix/store/5lv0fbn6ajwdzw04nz88cc0qqrgrvnp1-bash51-016.drv
/nix/store/4dq81yma6drk9701h17h64zx47r7p5n8-utils.sh
/nix/store/ds0q1li2i96dy7yp6n8zkbakz7m7d5l8-bootstrap-stage0-stdenv-darwin.drv
/nix/store/vcbpmcxpir9nyy480vx5sxb4pm2v0dps-bootstrap-stage0-sigtool.drv
/nix/store/5xk8j72a1dddq1gxh39amgifknwb0lvm-signing-utils.drv
/nix/store/fmd71yqpgwrkdbidzxwmfasqp39zzf8r-CLTools_macOSNMOS_SDK.pkg.drv
/nix/store/ijdrwgv6kv4k17qb5jvqkbs1ccqrlalb-bootstrap-stage0-pbzx.drv
/nix/store/z727aawh6imz3hpmviqyf4zlgprfn8zf-bootstrap-stage0-cpio.drv
/nix/store/fbhkqzn4wc69pa4rvndin5h4b8k02f5c-MacOSX-SDK-11.0.0.drv
/nix/store/72fl7wcbnl6srm5dxb7xbnn20zr07011-libobjc-11.0.0.drv
/nix/store/93n8xmgm6x65mh88amw15wps3h18yqn8-bootstrap-stage0-libcxx.drv
/nix/store/cyk47lyfswsxgn7z2qnhkp0919nhdd3b-config.sub?id=6faca61810d335c7837f320733fe8e15a1431fc2.drv
/nix/store/lmdwlh09b7g3kzga0i2hprcfxszg3ghz-config.guess?id=6faca61810d335c7837f320733fe8e15a1431fc2.drv
/nix/store/9vh7qbpb8chmx5r5cswrix00hngy7vky-gnu-config-2021-01-25.drv
/nix/store/2idjp2xdckqyrdk3hd8msp4cfdi6b8if-utils.bash
/nix/store/47gspvwaxra868q4rsmva0g5nz7zac6p-add-clang-cc-cflags-before.sh
/nix/store/797k3249lr4rx8wslf7kcsa3hv6fis3x-role.bash
/nix/store/bybz12bxjbk94hm58zc70sc0xhj2dxif-add-darwin-ldflags-before.sh
/nix/store/c1vmxz359mfljs3cdimhd2fr6fw0n99s-add-hardening.sh
/nix/store/civvq4xh4p0mj04l7k73p1xbsq1rs9bc-darwin-install_name_tool-wrapper.sh
/nix/store/dk3ly72kiv27srcj1mjr5n4112vx2hfk-setup-hook.sh
/nix/store/khkfggnk4h14f2spsjcjrxlf8himd4vj-bootstrap-stage0-rewrite-tbd.drv
/nix/store/ff3yqdllxmlp1r8mfkfgjf671r8flf8j-libSystem-11.0.0.drv
/nix/store/gvr0mz9wfz4g0z9w6477ikywmcivk1mh-bootstrap-stage0-coreutils.drv
/nix/store/jravmbdjn0md7cnn6rbqwf3aflw72msb-post-link-sign-hook.drv
/nix/store/js1lic1bmif60d4mlffkpbvz5jim34n3-darwin-strip-wrapper.sh
/nix/store/m0ww06j4y2v3jdsabrr8n0y9d4nnvqn3-bootstrap-stage0-gnugrep.drv
/nix/store/mrzpfh0ml9k07sw019ydagbb2z1q4sxz-add-flags.sh
/nix/store/vicx6qnvvxb96y0iwnya977viira2apc-ld-wrapper.sh
/nix/store/dj5xngrf61x9isyp2r5d84h3i43wg65l-bootstrap-stage0-binutils-wrapper-.drv
/nix/store/gn1b4gh07byi8xnahgc800jznyarqin0-bootstrap-stage0-clang.drv
/nix/store/hc3z14jhqg35x5286hw5wmc3injg62fa-add-hardening.sh
/nix/store/ji2yrl1na00bwav65hh1vr0nc0s1xzvz-add-flags.sh
/nix/store/lck6bijpq64zjvmkwdi081v6wm2r8nyx-bootstrap-stage0-libcxxabi.drv
/nix/store/li62b4bvg51zikbni9xadg08za340k71-cc-wrapper.sh
/nix/store/nc4bvrgb8jxh4k1fq2zgzh4mmxqavp54-setup-hook.sh
/nix/store/xbfsjb46np040h2aph1k76iybq9rzd7x-bootstrap-stage0-compiler-rt.drv
/nix/store/cz1x2bgvnzi0qc39hjwm0ppdvqwkfybl-bootstrap-stage0-clang-wrapper-11.1.0.drv
/nix/store/00qr10y7z2fcvrp9b2m46710nkjvj55z-update-autotools-gnu-config-scripts.sh
/nix/store/qzvw98z9qwv3vasfc9lwcq0d1sgfin6v-hook.drv
/nix/store/vp49i9krzqf282vj6bqr6rxs96d2a1fv-apple-framework-CoreFoundation-11.0.0.drv
/nix/store/lkjwmgmnm4f7d3iiglxglrgll1c8vdkf-bootstrap-stage1-stdenv-darwin.drv
/nix/store/r09r8wpshjqdkx0xwkin79km812nbgp3-m4-1.4.19.tar.bz2.drv
/nix/store/7rxh71ny4xrckw1ip50hv44mylpxpd5v-gnum4-1.4.19.drv
/nix/store/h1s43lrwjjf98dmfhayn6cmax2h19qz9-bison-3.8.2.tar.gz.drv
/nix/store/85f1q5rm0qzqay7fk3935h6kvzfqdcfv-http-tiny-verify-ssl-by-default.patch
/nix/store/dm81j9qdcdr4c458pqbc9wvq9ymgzk4m-setup-hook.sh
/nix/store/mb381gpm9k5wdl37l5ad5pp8w11qzhjg-no-sys-dirs-5.31.patch
/nix/store/n315a3g9bcxlypqzbm36nzrrg40h6lcj-cpp-precomp.patch
/nix/store/ppbkcbhzwzwpqaad6whhr9qgxjvj82gj-perl-5.36.0.tar.gz.drv
/nix/store/0phjl3yzr4m1gady21l21h4brn140hjm-CVE-2022-37434.patch
/nix/store/jw5f744vn0nv6q0ms6s5q0w0kkml2a8v-fix-configure-issue-cross.patch
/nix/store/mg7z3nsc96864cfkx2jwda7wmpdn71dl-zlib-1.2.12.tar.gz.drv
/nix/store/xniqbx8mh8lx06bw267g6hkfkp2c79kn-comprehensive-crc-validation-for-wrong-implementations.patch
/nix/store/vblzwf77rk1mxxk6qjhgaqp43z36j992-zlib-1.2.12.drv
/nix/store/x0ll5lnagy6lg7hgvqb26il3qnmsqisd-sw_vers.patch
/nix/store/m9h3rw3jlzf8hapgiw29i5fjyzw7r9v4-perl-5.36.0.drv
/nix/store/5nxxxmzb23y9pzp673grmfs95jrx14rx-bison-3.8.2.drv
/nix/store/13i5k09s5g6gdkaj731rqsi1qc70khka-bootstrap-stage1-stdenv-darwin.drv
/nix/store/jik02mkz72r2f6hhxnlhp6h5f0fi89gw-expand-response-params.c
/nix/store/g9h3q3y44p4ycn8vdai1s9aw1f0s7icy-expand-response-params.drv
/nix/store/734l1nlc4d2zbksafqvp5436ynp26z3g-bash51-014.drv
/nix/store/crar8b49gsplly9x0v279bibvixmj9gc-bash51-003.drv
/nix/store/d7i8a7c8mb0qmsb1c3rj8g38m071vmaf-bash51-004.drv
/nix/store/gapa7v6rz1gzjsn1kckpb4fj07pmznw6-bash-5.1.tar.gz.drv
/nix/store/ig7d802vp43ap4ga26r2vd174h2c1bk7-bash51-010.drv
/nix/store/jj3jm1bjqnlwz4yassc9h70mwmrxy442-bash51-008.drv
/nix/store/krhwn1440gxvry5gw4wmzk6y83fm4bdw-bash51-001.drv
/nix/store/n92wsf556wqp4dbf0rxwx1b175m0vwyn-bash51-013.drv
/nix/store/nkzvvyvmckwv4a8r84iwyfbivvw05nvc-bash51-009.drv
/nix/store/nm5946y3ffkynrz67vz47ik0ygvg6byn-pgrp-pipe-5.1.patch
/nix/store/pkd2vxq8w5fhrfk6k89hi29d5ldbbj29-bash51-005.drv
/nix/store/q6jpdv2j5dh4nbsbmkzy6za1vfpb2l30-bash51-011.drv
/nix/store/w8q9jdvb96sayxyi25amd27yki283zg9-bash51-002.drv
/nix/store/wg9gacyfgldnwh3gc23nr49n4j0b58sv-bash51-012.drv
/nix/store/4kpfa2fhfdb6r9icym3q8n3w1s8mfv6z-bash-5.1-p16.drv
/nix/store/k9bkzj7nhqxnvbmz5vk8pmzxpmym0qa6-file-5.43.tar.gz.drv
/nix/store/hgps4nk9p01z9zngmbnbv42pqmzg9nhg-file-5.43.drv
/nix/store/4cmjzk8yr6i5vls5d2050p653zzdvmvp-setup-hook.sh
/nix/store/4hcdpxjmr4nh625ry840g70xp00vdf5a-2.71-fix-race.patch
/nix/store/kxdvjnq8cl9jd5g9jndhhl5a17h2xbih-autoconf-2.71.tar.xz.drv
/nix/store/irpqw60zcy00lj3hjia8gr15z984x9xn-texinfo-6.8.tar.xz.drv
/nix/store/19ndr8g629l0xzzs1x7xz4z0fbkwpgcj-xz-5.2.6.tar.bz2.drv
/nix/store/kas3n4g0jyrz3rhl1znjcxqmpyddk8sw-xz-5.2.6.drv
/nix/store/5pj1w63j668yqssrxb4dykpzpm1yhx9q-libiconv-50.tar.gz.drv
/nix/store/xrvbfm0ahaiik1vlxzn3vsp5gyhksy2n-setup-hook.sh
/nix/store/5xx3gba361rf696b27r3jfa0q7rmlyh9-libiconv-50.drv
/nix/store/ny42y6hs4p294rvnrwbmrpwzqghw2816-gettext-setup-hook.sh
/nix/store/p2fp6i7hjx9af1wbwr32k217wp2dxmiw-absolute-paths.diff
/nix/store/sihz0cdcajckxnhjm8n4p652sbd5xwxd-gettext-0.21.tar.gz.drv
/nix/store/ri729qz1iq3iqr9bdvb406izklj1fpfa-gettext-0.21.drv
/nix/store/yl6g04fsr6p9lkvccqjjl8q1xnnmw49s-fix-glibc-2.34.patch
/nix/store/v94cwmp6hs6dvrw6v4jmqk9jwl0ik75q-texinfo-6.8.drv
/nix/store/ih062232k706fpydng4xlk75fpzraxmn-autoconf-2.71.drv
/nix/store/r5kd1di71n7xk9kjvrgyy75cz0c0skay-automake-1.16.5.tar.xz.drv
/nix/store/hqf9nllss9z0i3gmi8sv9kjcm9qhvf8q-automake-1.16.5.drv
/nix/store/ipdgmqcfn56mhgmq3hv3yy5xj2kj2ri7-libtool-2.4.7.tar.gz.drv
/nix/store/06nfwja5j8c36n780jvlwjv8gs2m7i3m-gettext-1.07.tar.gz.drv
/nix/store/qf3mzpvsmkrw963xchbivcci06078n13-builder.sh
/nix/store/j7jrmh8zz3jjcdsa050f1ja19nm07vz7-perl5.36.0-gettext-1.07.drv
/nix/store/lac4y6zxaqqhyf4r60p3ag68k3ckifvv-help2man-1.49.2.tar.xz.drv
/nix/store/k9223a4j2ar7j76zpjs8fmlfl9q76a76-help2man-1.49.2.drv
/nix/store/9gns0vpn7fajyzm3w2rsdbcz9pqgfsrm-libtool-2.4.7.drv
/nix/store/ghdamd4hl6yi7jysh1x3436fj1v9yvjb-autoreconf.sh
/nix/store/8byqd66k9p5zbiggz2a9frki47xqy28r-hook.drv
/nix/store/ing5qg4q87wdvm1h455c3xn889m2bbj0-coreutils-9.1.tar.xz.drv
/nix/store/66ayp7hm682rxhlxd061fvprrmf2zx3c-gmp-6.2.1.tar.bz2.drv
/nix/store/h96aykl4imzhfapnmavplr49v43c8apx-6.2.1-CVE-2021-43618.patch
/nix/store/k49s9lr0lw5zhwsz4ni4rf2643byqrak-gmp-with-cxx-6.2.1.drv
/nix/store/jp5jbqfxjazf38w63bipqf68h7hyq4v0-coreutils-9.1.drv
/nix/store/322332kbmj7ig7ii3cwlfjcg4mf5xgz6-grep-3.7.tar.xz.drv
/nix/store/m2qc8a9c4yr5xmqck50fpzzwzpqggbbw-stacksize-detection.patch
/nix/store/qk3pnajspb378zx6c8g0anj2d7z31a88-pcre-8.45.tar.bz2.drv
/nix/store/pgzgn3knxdn335qci805a49bvlnl4ppa-pcre-8.45.drv
/nix/store/l2bbsavfww8zv5a9ncldh83c9vsz3y01-gnugrep-3.7.drv
/nix/store/1igs2sr5j99180z801426ivzzbikxi7q-CVE-2019-13232-2.patch.drv
/nix/store/1k1wn8807yizgz3ghnbd4k6zsc0dzfkr-CVE-2014-9913.patch
/nix/store/2cq4hsc1v8ylccspw8351r72s56w1fia-CVE-2015-7697.diff
/nix/store/6np2acjv1nxcg0xzsv9a76wyrpxznkna-CVE-2014-8141.diff
/nix/store/6zqn6w9rwkgfa6z1hpagnh5xhz2dag6m-CVE-2015-7696.diff
/nix/store/97d26l91h0db8h0qkmhxwi5d8shrilv6-CVE-2016-9844.patch
/nix/store/p67r2s04xw8plqs0552rc5qyj5016wxb-bzip2-1.0.8.tar.gz.drv
/nix/store/ra4ikm6b0nxgi0sil4mk3ikkingm80x0-bzip2-1.0.6.2-autoconfiscated.patch.drv
/nix/store/awrvprhqr5j0n6n8zqss2i5cyv25wis3-bzip2-1.0.8.drv
/nix/store/bqwhj80hz1z49365miaqcxp4c2mzsfp3-unzip60.tar.gz.drv
/nix/store/cciw7lgkldvx25d77cxpjhh1iw4xghd9-setup-hook.sh
/nix/store/d9b2qrrq32jzdsdx4y33inzrra5n5z5n-CVE-2014-8140.diff
/nix/store/ly8k93l59mlzlgnq679vcp54nqpi4sdc-06-initialize-the-symlink-flag.patch.drv
/nix/store/mwkywhh9wvym79lzlk8gsanw5swhfb8w-CVE-2019-13232-3.patch.drv
/nix/store/p46prhgmv7ibjh9igfkbc6zfxbbi6sk5-dont-hardcode-cc.patch
/nix/store/p55a764pi2f4kkx3adb43bxb2dnb4z6r-CVE-2018-18384.patch
/nix/store/pdcj2chp5c2gvm2jc3shbajfc62kbx1i-CVE-2014-9636.diff
/nix/store/rdkdki1f24q8mqgnbsyk7gmh28c027ks-CVE-2014-8139.diff
/nix/store/wx62rx7078a86mpimgg5gfsiisx3qk5l-CVE-2019-13232-1.patch.drv
/nix/store/zxpjddlgb46cdynbgbgq41i6k9a40wfg-unzip-6.0.drv
/nix/store/cnds925pfnac943p1f516pxwrbqy0gdi-source.drv
/nix/store/2cqnhs2ds0gn7xrq0zcwbj7gsv6l5xr7-use-etc-ssl-certs-darwin.patch
/nix/store/2yk6ab4c6j9y00g3x3cixqgi9jmgiwzd-openssl-3.0.5.tar.gz.drv
/nix/store/6hyy4ngzkxdsg71zmryvy3xkw0ydag21-openssl-disable-kernel-detection.patch
/nix/store/sq4h6bqjx12v9whvm65pjss25hg1538q-nix-ssl-cert-file.patch
/nix/store/imc07k6kg26rcam012pkgxba3mj0c0xq-openssl-3.0.5.drv
/nix/store/1ksmnsr3m6paw8gs7jp9b623agzdrqi2-add-flags.sh
/nix/store/9whvblgb7bgnmqsv7y5xqs1w8xv9aa1d-pkg-config-wrapper.sh
/nix/store/c4akajrb4jg50k72jw7zfbyv8z139ri0-setup-hook.sh
/nix/store/f4bvwqvj0y3z6blvh0knz71a8yq1c45p-requires-private.patch
/nix/store/qycyfzgf92i96ygij2b5bz27ll6mkyyw-pkg-config-0.29.2.tar.gz.drv
/nix/store/dq5y5c22p6ixswcqaxb6s0ymvi2bwmlm-pkg-config-0.29.2.drv
/nix/store/ssvy1s8p78q1jfy7bap0kl49sm8ad0m3-pkg-config-wrapper-0.29.2.drv
/nix/store/xz6rbdix12jn1bd3lydd1cagnvr94yf4-sigtool-0.1.2.drv
/nix/store/gp50m2w3aab5w1pgx9h18yky5x5jzzyq-signing-utils.drv
/nix/store/gz78khbgfz3rh5phvq1bavdp4v0bcimn-post-link-sign-hook.drv
/nix/store/zp333xdrvsgn3mjicwcbfiqd259ix6gd-MacOSX-SDK-11.0.0.drv
/nix/store/x3wxdzi7f36fnb1sryr1ifgafsycasks-libSystem-11.0.0.drv
/nix/store/v77imik30yacsdbfqkkdv0djyk1hsdgh-bootstrap-stage0-binutils-wrapper-.drv
/nix/store/6cqn1ln7v306ymslgmvcy77pbq30pjy1-bootstrap-stage0-clang-wrapper-11.1.0.drv
/nix/store/3inq067xw7bic7dy828bgjjzx54kav9v-install-shell-files.sh
/nix/store/70pf3jk5jc64jc82hqck1jx6z5l42xj3-install-shell-files.drv
/nix/store/zwlzlqvh3x2nw1421cvx2210bzz3xgfn-libev-4.33.tar.gz.drv
/nix/store/gn80i99rcc85d7l687d1jdmcm0yl51yv-libev-4.33.drv
/nix/store/h60a8qk9wqy8gbdvl9sf0qcnz63by9w4-nghttp2-1.49.0.tar.bz2.drv
/nix/store/09ba5xcaaaybqdsvljh5skzzl6blmmw4-c-ares-1.18.1.tar.gz.drv
/nix/store/l0niywhmkdqv97i8rlxzr1yqbn9k7sc1-c-ares-1.18.1.drv
/nix/store/36rbachr8ra090v3m6s86603sfp382k4-nghttp2-1.49.0.drv
/nix/store/fgr4mizzn2y712bqlk895lk8wpws27ir-dyld-433.5.tar.gz.drv
/nix/store/7c2sh9fyqz113rrpx5mdnmkk8mdrhb47-dyld-433.5.drv
/nix/store/81gwh57vnrm6qpw3gxmdmrjsp26dxi7p-find-xml-catalogs.sh
/nix/store/1vaq58n8rvn8bbr9mcm3k30zkr63480d-hook.drv
/nix/store/68bzcamhirzd95vsh05wilz8p8vfvyb6-expat-2.4.9.tar.xz.drv
/nix/store/0vyw0ywxhng31zvxqia2y23ljjmzsdj6-expat-2.4.9.drv
/nix/store/345r2zz7pgiyk91j89qlf7mhs95jrv6f-no-ldconfig.patch
/nix/store/zi0m9pfmvy5lw89x7a8x674rm99i8qiq-setup-hook.sh
/nix/store/4aakllhrfd3r87a67g0hb6r37nk4ahqf-python-setup-hook.sh.drv
/nix/store/4j7gbzbahn5by6vvck1gcpjc95k6vpz6-Python-3.10.7.tar.xz.drv
/nix/store/9m54l1bi5814x9cqznwlga7yfs5ipi6h-nuke-refs.sh
/nix/store/pm08hy0dzswr0wj3n7nczm5pbzzjxdh6-darwin-sign-fixup.sh
/nix/store/4qmyys335vfbmyx2q8ii3md77yaswsim-nuke-references.drv
/nix/store/57kclla9vza2n87xgwg1ap54d20cz6lb-fix-finding-headers-when-cross-compiling.patch
/nix/store/aafa965mg7grhivxa01mmbqksz6c3w77-python-3.x-distutils-C++.patch
/nix/store/cv1ynpzvjjr0s72jkbblbzz3ymr87lpi-0001-On-all-posix-systems-not-just-Darwin-set-LDSHARED-if.patch
/nix/store/dkb2rjyj7lwmvsn4zzwx85kx8r61nk9w-darwin-libutil.patch
/nix/store/w7hflmy5kddj6p1kvbkgag7qjs44217d-libffi-3.4.3.tar.gz.drv
/nix/store/nv7ln6adf9vv2c81rw1rv8sarp2w3dbp-libffi-3.4.3.drv
/nix/store/r112dk8w7zvdjipki58ch00m825li7fq-virtualenv-permissions.patch
/nix/store/x6aiw4vay2b63slqz5byimn0alhg5b1s-darwin-tcl-tk.patch
/nix/store/70zdg3iypjrxjyvj602ai92j36j8l4rp-python3-minimal-3.10.7.drv
/nix/store/1qrnbw8xsww3vydd71lwfp32ylgx9i8g-make-wrapper.sh
/nix/store/819fzxfwzp7zhhi4wy5nkapimkb1bsx5-die.sh
/nix/store/csphakh9ck533qnjfqmrh5ybg7amwvwv-hook.drv
/nix/store/scgakk3jkkkqfzxp5v9575163dj03c2y-hook.drv
/nix/store/8p4sgrqajxwzcm9l02m45qvpg6ncr0h9-patchutils-0.3.3.drv
/nix/store/baz2nahq7z7xxya1gi7in6a4msmvkbly-5b2d07a72670513e41b481a9d922c983a64027ca.patch.drv
/nix/store/frr6f2hq56j6b4wc0bsklaqskjsagqc0-utf8-xmlErrorFuncHandler.patch
/nix/store/vyh448kgn8qx2frx4g42f64b1akic218-libxml2-2.10.0.tar.xz.drv
/nix/store/lyl7058saady3i75h1w0177qfzfr1zf0-ncurses-6.3-20220507.tgz.drv
/nix/store/wy5amsi09scnbxgsbm1fzzq9963zlg9m-ncurses-6.3-p20220507.drv
/nix/store/x1fa0bns4szjkbb7f8bsflcxmzas41h0-4ad71c2d72beef0d10cf75aa417db10d77846f75.patch.drv
/nix/store/9gql9xbn6mfd56lxxchd0q7qzyf7cdby-libxml2-2.10.0.drv
/nix/store/1nv6k9zyc7pj74yshdzm4bmjfv6k86l9-source.drv
/nix/store/34qn4by9lqrri323ahm5vizv6bgsbhfn-006-darwin-always-set-runtime-c-flag.diff
/nix/store/chhz1kdhnql7gshwrns13lawgm75an7c-adv_cmds-119.tar.gz.drv
/nix/store/bylhfm7hj9sm4qb5n9mnsvj71xhmi6rx-flex-2.6.4.tar.gz.drv
/nix/store/vila3sxd48ha6r4yhdbfzqlvfl1jn2bw-glibc-2.26.patch.drv
/nix/store/lnb1468vfblvbc6lqgpigypspa4lzl68-flex-2.6.4.drv
/nix/store/6s064qz2lzpi1ml9z4vx0ajmk7fwafv6-bsdmake-24.tar.gz.drv
/nix/store/pps2jxig0cgkz81qwhy9gqar360mbbdb-bsdmake-24.drv
/nix/store/x1dh5wnl7pf81iq0gx0gqj6i9vnz89vv-source.drv
/nix/store/icl9b9dikbnwsrb5agnjmfryxyjnfp1n-adv_cmds-119.drv
/nix/store/6wbmq1k5x7q9lchs986bjq2qf1ip7b41-ps-adv_cmds-119.drv
/nix/store/7qslhycy1d0ag13xn4rfxfwdm8f7afb1-001-search-path.diff
/nix/store/ar1p4gcvlqf8dwbkfrcb01srbywinaj5-check-pc-files-hook.sh
/nix/store/ha21dnn4nw858g8d0wrkvhyvp7zmqgnz-setup-hook.sh
/nix/store/jmif3w9zsykc13zrhw4y8pynnnxg27zn-cmake-3.24.2.tar.gz.drv
/nix/store/k845qxmkbra9fc4628lpi0q8yjmc1sx9-005-remove-systemconfiguration-dep.diff
/nix/store/ri7qddqm02y0w2g43zqqz8lzy2pbdgrk-002-application-services.diff
/nix/store/rq41inv92jszcs5x62nc5cci5hapbjpw-003-libuv-application-services.diff
/nix/store/23dflh1nkw215df7bfyy9ml3jd5ranrw-cmake-boot-3.24.2.drv
/nix/store/c14fh88kpi7gc627s1l19gdlipfrwd59-brotli-1.0.9.drv
/nix/store/rv3w6kch2d0l70r6h3kk2r5d8ca216hz-libobjc-11.0.0.drv
/nix/store/f07bc31w87jm2rjk8zap5xyf92ach79p-apple-framework-CoreFoundation-11.0.0.drv
/nix/store/k8xvsl4226pzy73ywglgx46h7scffpjq-gnu-config-2021-01-25.drv
/nix/store/h9gslm9dppn38plwa811yr5c03s5lw9w-ICU-66108.tar.gz.drv
/nix/store/mqvcwr74dcsqlib5crdcawk16dmdgj9h-ICU-66108.drv
/nix/store/rkr3wamhhf9ha9n89yimjwyazvf3ar6j-hook.drv
/nix/store/f0qv4kz20212qcnd9wsck36zk3r8isl9-curl-7.85.0.tar.bz2.drv
/nix/store/gdqnvkbp6nnrvww108adb7nvjgrpdxzb-7.79.1-darwin-no-systemconfiguration.patch
/nix/store/3bld52y6l8fg58gaj9b937qg6m7zbm16-krb5-1.20.tar.gz.drv
/nix/store/3xk9ps0qz073k641b88swpa4fgx3hzwg-nix-ssl-cert-file.patch
/nix/store/n3vnxwsnf75gz250yayipdga4ziidwpi-openssl-1.1.1q.tar.gz.drv
/nix/store/82cq1qadbgv5d6gxb11zgkxl530wvzxj-openssl-1.1.1q.drv
/nix/store/18vddqgdxg6xcv5iws1vv7fd152rag04-bootstrap_cmds-121.tar.gz.drv
/nix/store/jyci5k74rnj58fkalyil3pj28x7xnr3m-bootstrap_cmds-121.drv
/nix/store/hj7zxy2r8ib6s6fa669kr6hqdiv4l6s1-libkrb5-1.20.drv
/nix/store/z3h1d8wp61892ydalxldvb496ar0hiz6-libssh2-1.10.0.tar.gz.drv
/nix/store/zrh6il3gp9xa58ldg40d57kwgsvljyb1-openssl_add_support_for_libressl_3_5.patch
/nix/store/yf97cpb80lyvqgd0lnq82c4qkx7kpb9f-libssh2-1.10.0.drv
/nix/store/w5683845xkfzwlp8sgbax0farj5fzhpv-curl-7.85.0.drv
/nix/store/9rkj0y4cl1sbbzndzg01crdamv4813pg-bootstrap-stage2-stdenv-darwin.drv
/nix/store/labgzlb16svs1z7z9a6f49b5zi8hb11s-bash-5.1-p16.drv
/nix/store/hvl8g8b6n8m8dk16bdavvpg31g8zmk96-bootstrap-stage2-stdenv-darwin.drv
/nix/store/9lzpvga5gm5klwg28iv1hgf01g0hpfaa-hook.drv
/nix/store/96fgzfyknjaj6fc85ai2n68qfndbps02-hook.drv
/nix/store/6wky968nz63ndx7z6kppcada8cdj4hg8-bsdmake-24.drv
/nix/store/0w9k3ah3f9c0j7k4vxka74pi07x01bbv-adv_cmds-119.drv
/nix/store/2n3wbbh9sk778vkl1lsz58inmyvk2g3r-file-5.43.drv
/nix/store/bcv1p9lpghn2l1zcw21z7401qc8fnmvr-gnu-config-2021-01-25.drv
/nix/store/47msbw943yjc7hm6d13r9jl5cz0ih9x9-hook.drv
/nix/store/24a06br9jvy87sbanym8hijfq4j2dsqm-no-threads.patch
/nix/store/4a0sl25fn3ymdmaalxwvjk6a2xajy3cb-libcxxabi-11.1.0.src.tar.xz.drv
/nix/store/ccf2fg1l77aqbw3v8hiql7a4c54dr8lh-bootstrap-stage0-clang-wrapper-11.1.0.drv
/nix/store/bhmp58x5m3lkscnxs9zwscc4rxbz7fc4-bootstrap-stage2-stdenv-darwin.drv
/nix/store/db7hhqin9klk8qx344r2b3dhb4xrfiq6-llvm-11.1.0.src.tar.xz.drv
/nix/store/il3mvxijf7cc9ys09pcblff47ia93kk2-gnu-install-dirs.patch
/nix/store/z3pj61rk5h9ffbj5anhwbp3ah2qysvqf-libcxx-11.1.0.src.tar.xz.drv
/nix/store/fg68n6bycig5c23sw8yfn7nmgbj85v6x-libcxxabi-11.1.0.drv
/nix/store/wjgikzdk0fcbldspzlkyh0dvd8wa2say-patchutils-0.3.3.drv
/nix/store/3v9a47xl4w8kc8bff4daxvbg50008imh-19bc9ea480b60b607a3e303f20c7a3a2ea553369.patch.drv
/nix/store/c9w69kl4caarcs2j84hck0icrdj9jqr6-fix-darwin-dylib-names.sh
/nix/store/g958ikb42h89wl8rgx597l5h6k9n2cfx-fix-darwin-dylib-names-hook.drv
/nix/store/ym4y16msxvvpbcsc0s829has6v8mxg56-gnu-install-dirs.patch
/nix/store/hhn96pp2rk2bq1hipcr147hqfrgh80gk-libcxx-11.1.0.drv
/nix/store/rizgpw7ndpwy12kyr03mnlhhkfwzk75a-pcre-8.45.drv
/nix/store/m1pm94jj46gsmks3d11p44qdhqml9inm-gnugrep-3.7.drv
/nix/store/r2mi8209hbvfhyhjcxy6qqvyawf8s4k3-expand-response-params.drv
/nix/store/6yjiri44dy8c8lnjn0f14w8nvgs2fhf0-bootstrap-stage3-stdenv-darwin.drv
/nix/store/dlm9y4dfkjmcqrysf37kxfhlds9r9jng-hook.drv
/nix/store/q6x1zg4xsbzw7qv3qdcq2rny1n4pr5xk-hook.drv
/nix/store/iraif23i0p01sx6qq6jkry21v7g84wvi-bootstrap-stage0-clang-wrapper-11.1.0.drv
/nix/store/wpni47ni5xf8qms83in475fyn8z2ikf6-bootstrap-stage3-stdenv-darwin.drv
/nix/store/40ri4k2mfvs5wjwry47iqv30587p1jh0-patchutils-0.3.3.drv
/nix/store/0w90wllxcb2wyjbkxjacm0m3q2wfz702-uops-CMOV16rm-noreg.diff.drv
/nix/store/09q0yxa6ixb030mkw96j2za7h8dpbpc7-dejagnu-1.6.3.tar.gz.drv
/nix/store/fk7p458jm8ra2d6zf2y4nw1ykckvmnrr-tcl8.6.11-src.tar.gz.drv
/nix/store/5z49kw0iq4qyga5zxmmhw1fh2l6jiwjz-tcl-8.6.11.drv
/nix/store/g4c3jbhc8ag6db5py0xk2sicfy0hrpmw-tcl-package-hook.sh
/nix/store/a1ipqs2qcpbqyfmxgk6yi3yyl2f6pd62-tcl-package-hook.drv
/nix/store/4vq3350zc6sqnibkqpgic2d6cvi1r9hq-substitute-all.sh
/nix/store/5241l4i852qd9imqz3jgv9rv9gv701gk-fix-cross-compilation.patch
/nix/store/dhba38jjxia0b5snjrhvcl9dbjbdb3k0-fix-cross-compilation.patch.drv
/nix/store/gdag5rjmiv9iwgj8gnibcvzic5f1kwbp-0001-enable-cross-compilation.patch.drv
/nix/store/qcmyh5mgqv62zip6gkp2xjriklpbm0xv-expect5.45.4.tar.gz.drv
/nix/store/xpdirxij63a69jk43pgajjrgmv7gaajd-hook.drv
/nix/store/iag2icih50jb6wxn15a71jy9pix7jd15-expect-5.45.4.drv
/nix/store/nharms54shj3rhwzw2ywk4alfrgm5k1m-dejagnu-1.6.3.drv
/nix/store/2hc51kpv7vkbfpifjq28jbhm49wlphzw-libffi-3.4.3.drv
/nix/store/6wv8acdd7bm7npixgr3rk86mf55fj47c-llvm-config-link-static.patch
/nix/store/9fcwwdr92g5wzpj2wxf86f0zy5mn0h1v-zlib-1.2.12.drv
/nix/store/9fd3xdcfv1dw93y99a18lw735fx8kvgy-gnu-install-dirs.patch
/nix/store/g05jynqyglkf9if6y70822l2r6y3nkj5-ncurses-6.3-p20220507.drv
/nix/store/2mys4xnihsf6r5y0sbby0y3qcn22ggjw-4ad71c2d72beef0d10cf75aa417db10d77846f75.patch.drv
/nix/store/2nyygvchsc258gkqymnbzmh741wsizrf-libiconv-50.drv
/nix/store/i68qy2hwsvqx3haf36smqi3n4lg1wavm-5b2d07a72670513e41b481a9d922c983a64027ca.patch.drv
/nix/store/w8ma9ddjcfc3l3z4ng6rlwljxiw85fhv-hook.drv
/nix/store/j4h65frq4nx4kpl06sj5c3cz2lc9fdrz-libxml2-2.10.0.drv
/nix/store/kl8l1ci2ycc6y23vkxvjhlbz2p93zp3z-polly-11.1.0.src.tar.xz.drv
/nix/store/n1sl525i7qanfd75l8s1bbqn32fasma0-dfaemitter-gcc-12.patch.drv
/nix/store/qcb7ljbcrmgdf6xv0irgvdv6rivgj3md-nvptx-gcc-12.patch.drv
/nix/store/yzac3dpyb5cyndxjl0d9nrpwqmcz18dk-b498303066a63a203d24f739b2d2e0e56dca70d1.patch.drv
/nix/store/8vqfrpgwki8j8nk9j08g04vh1iqcm9bw-llvm-11.1.0.drv
/nix/store/2jlsizsg3hsj6p10cm11fx9rshsjwwri-clang-tools-extra-11.1.0.src.tar.xz.drv
/nix/store/34zhl915l308dpa7v2786z3xgbvy6398-purity.patch
/nix/store/akc6qlr586k4r5lwmj70i0mbb8wxz709-clang-11.1.0.src.tar.xz.drv
/nix/store/v2az1r12rfivd43pvq2s0brp60n54mqa-clang-11-12-LLVMgold-path.patch
/nix/store/bhdlpz3fgkzcrh94rsp76m0n0gwb6fxx-clang-11-12-LLVMgold-path.patch.drv
/nix/store/f91k74rxz9c8miqg843jf6dfq35jhpka-fix-darwin-dylib-names-hook.drv
/nix/store/yrskcbyfm56qm4vs9dnacg8mqpqx7qwx-gnu-install-dirs.patch
/nix/store/arqis7aqh6b9p3a90idcjnr21fp1d0c4-clang-11.1.0.drv
/nix/store/79xln4yb0zql3j1zvc72yfi1y6333crl-disable-rpath.patch
/nix/store/dvdql179kllliqznwkd307bvdny2h96h-source.drv
/nix/store/5ks4wyqmh20wzz3i2yyqky7g0g8jhmsk-libtapi-1100.0.11.drv
/nix/store/aipzgrzywrh1qgw2l3bigbnnwpyr61kn-source.drv
/nix/store/fz56qb3cymbf2acghsqmdgjwwklc411f-install-shell-files.drv
/nix/store/r9iy2wdkq3w0n1hbyg92c2sqg3vcbkhl-ld-ignore-rpath-link.patch
/nix/store/3jjd65b6nyg7g30ixszjcsgb08j5mmhn-0001-Add-useless-descriptions-to-AC_DEFINE.patch
/nix/store/f3ygraph3msfdkyrqg91j7smx1c78qnd-bzip2-1.0.8.drv
/nix/store/j3frsgc0zsrg2pg7w9pqmxjh6qd01aqs-0002-Use-pkg-config-for-libxml2.patch
/nix/store/y26ic2b9n1g1cng3s68753qcxd6fvqrq-xz-5.2.6.drv
/nix/store/yqdd6m0r80c5pn3z7wc1mhn1wqmyli4i-xar-1.6.1.tar.gz.drv
/nix/store/1m30bskfc8z8r4dhb2qv9sd7jyl92srx-xar-1.6.1.drv
/nix/store/7vs1bbfsk0w6p6n5c4bqs3mp7q6n9lyd-source.drv
/nix/store/paybkfdbyh40jzyv6l5ywsbaabqy1sav-pbzx-1.0.2.drv
/nix/store/vwlpvn89jh6h45dj8q9nin0iv7rw85qb-MacOSX-SDK-11.0.0.drv
/nix/store/vb56s92027cg2c6573dahs6830fhz9xk-libobjc-11.0.0.drv
/nix/store/zr59kiajvksmks3vl1hja2ybix9z2zqk-ld-rpath-nonfinal.patch
/nix/store/ldj50kzrjbzh8dzcb7mqqa5gqsmc33v9-cctools-port-949.0.1.drv
/nix/store/0df8rz15sp4ai6md99q5qy9lf0srji5z-0001-Revert-libtool.m4-fix-nm-BSD-flag-detection.patch
/nix/store/k1cgpjsl77c8wfaq0w7n6k1i7fvxr5p8-gas-dwarf-zero-PR29451.patch
/nix/store/lgniihp1bk6mkd5nn9y5ikfim2ignr52-0001-libtool.m4-update-macos-version-detection-block.patch
/nix/store/pa83jbilxjpv5d4f62l3as4wg2fri7r7-always-search-rpath.patch
/nix/store/dqgzlxvbzq4dih9ska9k0y91sc1kv7d9-autoconf-2.69.tar.xz.drv
/nix/store/s8wb99pw1w8yspcz26zfadsy0j1k70ww-autoconf-2.69.drv
/nix/store/sqbhaaayam0xw3a3164ks1vvbrdhl9vq-deterministic.patch
/nix/store/xrw086zw3xqsvy9injgil8n2qdkvkpff-0001-Revert-libtool.m4-fix-the-NM-nm-over-here-B-option-w.patch
/nix/store/zbdl2p9amxdkr9cqjq0yv6h0mr55lm3l-binutils-2.39.tar.bz2.drv
/nix/store/zki7kfvf2f0xdksq9hp004xz1hsxklz3-texinfo-6.8.drv
/nix/store/q1kz46q80wj4bfc314g5p3sylpilpv0i-binutils-2.39.drv
/nix/store/00fij0grbvf9svcxvyk5ys51qcpmk7sa-cctools-binutils-darwin-949.0.1.drv
/nix/store/17agldwh525770zc3w11sdkpdxq8xwjy-gnu-config-2021-01-25.drv
/nix/store/0kkx3whrs8zb85qwwvl42ax99hmk0xpz-pcre-8.45.drv
/nix/store/3qkad2sv1xwngyn4gmx473mzrjhs2jx8-gnugrep-3.7.drv
/nix/store/xn8bjkw4fmyr2xvk9higd54g9s7q2kdz-gmp-with-cxx-6.2.1.drv
/nix/store/hbby7876a2qhk93hlx5ppr3jpgm1lwkl-coreutils-9.1.drv
/nix/store/vj0ai85srlbfn0yvd6mdql2nxan24naa-post-link-sign-hook.drv
/nix/store/vsd7hdcfnzn5n4q44flcdiaa2lv58d6d-signing-utils.drv
/nix/store/z4gcxwcrzc0mkx0zgha8k4jr506kgns7-expand-response-params.drv
/nix/store/vnxdn7rjmsm0naz6sgb98flh437iff1g-cctools-binutils-darwin-wrapper-949.0.1.drv
/nix/store/5gm6pb695hmb0q26cyvmm0ish0p5yig0-clang-wrapper-11.1.0.drv
/nix/store/2cw54rrcb7plvq6v5hxsm3sb42kq6fk5-bootstrap-stage3-stdenv-darwin.drv
/nix/store/3ic95d8vv23cdj4vq7634zh5zcdsda1p-normalize-var.patch
/nix/store/3m53ki7pr92hacq8mghsldg1wc4wrifm-codesign.patch
/nix/store/706qcmh667cmjwffc4vbjs9c745c7hs0-gnu-install-dirs.patch
/nix/store/skwzly3754w50q8mzdpadz8bsfmn6hs8-compiler-rt-11.1.0.src.tar.xz.drv
/nix/store/y87vwjq5v1fi0d077xqrsnkqrax3p4iz-libsanitizer-no-cyclades-11.patch
/nix/store/ylv0v02l6panidz2hkh756fz363yc68h-X86-support-extension.patch
/nix/store/1ffkl3b9gb1qyvmz2r1633vzkhd5bxn2-compiler-rt-libc-11.1.0.drv
/nix/store/9k3fqaqk3k7m510nn57ynd4ngx2x4rxs-clang-wrapper-11.1.0.drv
/nix/store/mmdgxk0mpsq2lzlja8g6mrdjzxpc3wmq-hook.drv
/nix/store/s9rdswvsx68yjwfk57vlw5a34hbjsl2v-source.drv
/nix/store/hrp64mrc7ss3zz8gfl36jfq8fj8qwk2b-libyaml-0.2.5.drv
/nix/store/y7wqq0qnhnhd6hr5d2hxl967fjssfrny-source.drv
/nix/store/vy20n6yc3nd8d1yk0sqa6cah7dpyhv9s-rewrite-tbd-20201114.drv
/nix/store/sn11j0j89hflj7snivdlxvxawvrpzp01-apple-framework-CoreFoundation-11.0.0.drv
/nix/store/aawhsgywk753j170584pf3r6rlismpi6-bootstrap-stage4-stdenv-darwin.drv
/nix/store/qwxrck5ibwhhhsgyblc711vnvw5rx2ib-bootstrap-stage4-stdenv-darwin.drv
/nix/store/bs639bs7frzx60c8bh3nyzmak49zbc3k-hook.drv
/nix/store/cfkvf8l9jw3vrvkss3c5i1ccgnp86nv6-hook.drv
/nix/store/skm4ysx9pk4nnwx75fpk9vghzc130887-perl-5.36.0.drv
/nix/store/ma5p31xpqrcyk0z70l14m7cklw2zkb5s-patchutils-0.3.3.drv
/nix/store/lapsha9pivk9wvrwlrc482i2biq06gw5-CVE-2021-38185-2.patch.drv
/nix/store/ncfbi3qgbflyph4x2ngcnz2584kykzqa-CVE-2021-38185-1.patch.drv
/nix/store/pklid951p82izlw5f06w5yvpps1zwgxw-CVE-2021-38185-3.patch.drv
/nix/store/sp2alvzdl09796wpg2wdf68akiha4d4g-fno-common-fix.patch.drv
/nix/store/zmadr12vmal7mwlgy1w4w4x70lss6j73-cpio-2.13.tar.bz2.drv
/nix/store/d54hwx5g50niakv2lpb9lkp1jjk910q1-cpio-2.13.drv
/nix/store/4yn89klg688jxj8sidmzc84hl7ndcwkm-pkg-config-0.29.2.drv
/nix/store/416vhsxki7508q1ijs8n415fqmvjxyky-pkg-config-wrapper-0.29.2.drv
/nix/store/a736nzx1nigikprmk4ignqzndmz7ls0m-gettext-0.21.drv
/nix/store/2szis9v9c9dzazajlflfdc9jl3b0pihg-gnum4-1.4.19.drv
/nix/store/gbznai4iy45kxmlnrn56fn5m0x2rlspc-texinfo-6.8.drv
/nix/store/f33p3mlclvl3hhnnjsnzw70bshn7criz-autoconf-2.71.drv
/nix/store/dw19klga9vppaq2f6lzj9h7bl4c29mga-automake-1.16.5.drv
/nix/store/hizv414b6ky026kw5f4hwgm2lbxzh08y-perl5.36.0-gettext-1.07.drv
/nix/store/ccwg6bkak9j236lc6k0n713iyn12k996-help2man-1.49.2.drv
/nix/store/jx8mci3vwqi9qv1wkbrfpjya00nl285j-file-5.43.drv
/nix/store/lahdhba85rpm60wxmhxanq1dqi8sa5kb-libtool-2.4.7.drv
/nix/store/g69zz30gq1rb85b6kdz5iidxxbwp1bda-hook.drv
/nix/store/hb2yk99cw015si2ry1l1fygp365as523-openssl-3.0.5.drv
/nix/store/cv65milx0bs2fk1xikgrgrhvkafkpqdk-xar-1.6.1.drv
/nix/store/9wfpm5alc542isr7232gg4qg5njdsgdq-unzip-6.0.drv
/nix/store/ggp35l799v2ggwsk73sz61y7wg3kpqy1-source.drv
/nix/store/msvf870j9sr6lwzakv6m263gk0r7126z-pbzx-1.0.2.drv
/nix/store/0b2hxys6lxyh567j82addkw2k36qjzgz-MacOSX-SDK-11.0.0.drv
/nix/store/0c1ijhg1cq01zcrvnhg24vhm5qn47kwd-libev-4.33.drv
/nix/store/0fqp7dz3rlrrhqjfc69wkmii3j5y0g7d-make-4.3.tar.gz.drv
/nix/store/bqlikyzhzhsjfirhzgmps9p99mhvn9az-bison-3.8.2.drv
/nix/store/c3kdwmns3lyigqqm6c4czisv0n226dy7-source.drv
/nix/store/hp3krbr0v290hwgrcskls6kk545virpz-flex-2.6.4.drv
/nix/store/mdihpwmpbkv1wg4yw03d7wy3fbs0l45w-bsdmake-24.drv
/nix/store/ln3xll7z1avhm03k3m8cdllln7wdgrrk-adv_cmds-119.drv
/nix/store/gs103r2mxcsjs3sw0ibvlnsv54qcw9q6-ps-adv_cmds-119.drv
/nix/store/94y4s4a30p8qqfg8bxr9rgwkdc9m3610-cmake-boot-3.24.2.drv
/nix/store/n91acyjrlchm0snw0w16i4683pf788ax-playtests-darwin.patch
/nix/store/vfb2ll7c9aq63mlkkvmvfq4ibiinq5nh-source.drv
/nix/store/vhp9nf4r8328m91l9l5c8fd9wlmb4bnd-fix-darwin-dylib-names-hook.drv
/nix/store/0w2r0sw68fwxqqki50mqx83iz1q6clgq-zstd-1.5.2.drv
/nix/store/4rj3r6gga5ipdfkiw9ahmrj9yr411ry6-libobjc-11.0.0.drv
/nix/store/vc3jlishkiy0qsw95smzzzr9vcgafi56-source.drv
/nix/store/jhs384scb8wk3sn9dw92khjpay91x9mg-libtapi-1100.0.11.drv
/nix/store/ylsnxqqfn3gpp0wr133z4ksj2bhklrg7-source.drv
/nix/store/z8gyza1abwd7jh3pv10r6kcrgphi2h2r-install-shell-files.drv
/nix/store/0xwx3m2lvpw92w4j45n1772f3aimskxg-cctools-port-949.0.1.drv
/nix/store/0y5flakfvnf813cwrr8rygf1jnk0gfnc-CVE-2019-13636.patch
/nix/store/4r8s8hcwyvvvnpcncps09zscqkh5qapx-no-install-statedir.patch
/nix/store/bljrd66ff2vp1zqikdfrz5x0k90kaw81-findutils-4.9.0.tar.xz.drv
/nix/store/10zrhmiqirncfbxyac8xrjg6p8mqf30k-findutils-4.9.0.drv
/nix/store/53d5wfhiifvxzgj847fva6q6py02m1g1-expand-response-params.drv
/nix/store/c2yrfg597pjcl1867pyir9jiq4fw3jl4-source.drv
/nix/store/9h8631c24qp90y7w0fvsl3c0dv8pv6pi-sigtool-0.1.2.drv
/nix/store/anpnd9wprrqhf1fdcwy96j66vb5fcsii-signing-utils.drv
/nix/store/5xzxmr5xpnqx0b0ar0kpy7kw0282a1jc-autoconf-2.69.drv
/nix/store/a921f1jk651ahri5f05gca9rwdiq3rlz-binutils-2.39.drv
/nix/store/laack7baw4rfxgvyqfrfydbn1v8zz9b6-clang-11-12-LLVMgold-path.patch.drv
/nix/store/d7h3zix26zzgdp0rzsyb19hrif8cbzmz-clang-11.1.0.drv
/nix/store/jxpxgb5hph4lw3s12g7lyqz0ci8a37ra-cctools-binutils-darwin-949.0.1.drv
/nix/store/w3hhid4crxafa1j03iq4a1y30d8p0sk2-post-link-sign-hook.drv
/nix/store/5w0k8f3ialhwr7p5g0y94zny3j74ryzn-cctools-binutils-darwin-wrapper-949.0.1.drv
/nix/store/74fhpxplxsc5qg1c67hb5picw88flx3d-libssh2-1.10.0.drv
/nix/store/76nc36kgrvhx8n9m8jz7ywrrqbcdkr48-gnu-config-2021-01-25.drv
/nix/store/9wyqdma0i671db7l4m0a3qbp6jpza2vi-tar-1.34.tar.xz.drv
/nix/store/90phqk429ip6kbi5jlm0lcg4h2xxaq2k-gnutar-1.34.drv
/nix/store/2j0fmwgm5ybgnc8jprc4ypcxw6s4r2nv-Allow_input_files_to_be_missing_for_ed-style_patches.patch
/nix/store/7mq3l9rhjbmpf06fwnrp51q6sy1l6g9j-patch-2.7.6.tar.xz.drv
/nix/store/8p3z4jsrxr5ck92iasc9bc7bmapb5mmg-CVE-2018-6951.patch
/nix/store/h2fcbw7ghgn3i4qadszdp272w4dab7ln-lzip-setup-hook.sh
/nix/store/vvciv7wkw3z9x0bj4jszb31crk0lix8y-lzip-1.23.tar.gz.drv
/nix/store/6gj38dbipcd2vbjcsv028jmpnn6bv1sz-lzip-1.23.drv
/nix/store/bv0xxgk72g693vdgs3w2w3d252hlxys9-ed-1.18.tar.lz.drv
/nix/store/lmssb21nd3zkv5gssngmk92bdf0q4h1w-ed-1.18.drv
/nix/store/npqvgz8c8w9kpj1gdma5bbn0pdkisyzp-CVE-2018-6952.patch
/nix/store/rxgi2l6jrgd5xmrrsbcv5cwi558lb36m-CVE-2019-13638-and-CVE-2018-20969.patch
/nix/store/sz6rhpf50kqh75fhqwl75q6dm6fr9xyd-CVE-2018-1000156.patch
/nix/store/aqmy225ay8m4yg51mib0bkpz3r1w2z7j-patch-2.7.6.drv
/nix/store/2k52bklbjhhq47dn35gm833vlh06fgfn-0001-No-impure-bin-sh.patch
/nix/store/6cc64ayl3fd2nc28ffw47cqsqi2bg1sn-0002-remove-impure-dirs.patch
/nix/store/avhna3r651j0frjk7jhy771za84mlh4j-gnumake-4.3.drv
/nix/store/b7mkkj1kbaxpihqh3k12s4a3viz9pdvk-hook.drv
/nix/store/dy63w31j33lknbd95fl81f24sndlgf35-source.drv
/nix/store/bl8jncx9dy4rr54cn8p1vwpf3wa01yyj-brotli-1.0.9.drv
/nix/store/rp4wqqv22pjl2235ra7ag3nb7yy0b5kr-diffutils-3.8.tar.xz.drv
/nix/store/d554acfihg4ssgij79ybd6ls0ww1p14x-diffutils-3.8.drv
/nix/store/szfjhkhmsxfyc289vz39882d3l866888-fix-error-darwin.patch
/nix/store/z8912zv98rl5yv104mf99j3k72xva3nr-libidn2-2.3.2.tar.gz.drv
/nix/store/qhs49gbxssyr8im8h2xc058gh4kdndv6-libunistring-1.0.tar.gz.drv
/nix/store/zipavm2mq94fsw09kx9mw7pi5n8xhkp4-libunistring-1.0.drv
/nix/store/gzy4kvn6djdm9b631fcm3g76pijsvvq7-libidn2-2.3.2.drv
/nix/store/hvp22x3rmpwfj6kcf0hj9dcv8lrkd4hs-clang-wrapper-11.1.0.drv
/nix/store/xhmwfi4xij5ryg9x2j0n6067ki55dscr-c-ares-1.18.1.drv
/nix/store/qpsbvsn7dl8gmlsmh474m2h9gmh8mg7i-nghttp2-1.49.0.drv
/nix/store/lwhmzpa1py1k244hfd3l026kc7r7b6ib-sed-4.8.tar.xz.drv
/nix/store/rvq62srcss99303nbgr94bq4av9kjv8q-gnused-4.8.drv
/nix/store/pid9is7y02wzdplvk6jcw2n0vfdh2y32-openssl-1.1.1q.drv
/nix/store/pz4hlrs0xa3q9knmn83v8v3wpf88iigz-bootstrap_cmds-121.drv
/nix/store/6m3w5kmj2m73nj3pakh6kaqjs2k64p5m-libkrb5-1.20.drv
/nix/store/sa6lxl61bh1wmr9rbpbyiq5wg2y5kbf2-curl-7.85.0.drv
/nix/store/vff2y41m68f5garsjmjxc0xfjrvw9pfm-setup-hook.drv
/nix/store/w99jp1rsykvc8rb09hr03c0rakw2dgzc-gzip-1.12.tar.xz.drv
/nix/store/swj6qdzryq4ln3h172s4h45wf0ks7g4j-gzip-1.12.drv
/nix/store/v994hjy8kmwi7g5li2lrpss92ldj0a54-ICU-66108.drv
/nix/store/pmxi9k28qls2yr7jhfnz3qp1fjchy64m-gawk-5.1.1.tar.xz.drv
/nix/store/x9ndmqlkrngf4jdy4zmvmal9ma7gh3z0-gawk-5.1.1.drv
/nix/store/5pfijbmkmy5kc81yzp0lpm4gpm2aq5rk-source.drv
/nix/store/zf7v9n0hxgb302wf08y762s0rhsbnqd2-source.drv
/nix/store/qr3r1g7n6xppyxb88726z7yr6r246nzd-libyaml-0.2.5.drv
/nix/store/91bfp6zivp9jq9sqq6iqq7vdpghdaffa-rewrite-tbd-20201114.drv
/nix/store/ya29pfy418vy1l0i5symnyprdb80c8ha-apple-framework-CoreFoundation-11.0.0.drv
/nix/store/cdk3pz11mvhqpphr0197wwmzhqppn7rl-stdenv-darwin.drv
/nix/store/26z459l0k3znhr99dsshkzj0il8dhwxx-perl-5.36.0.drv
/nix/store/sbcibnd6hym9c2rlbfnyhrbmlvgmsa59-stdenv-darwin.drv
/nix/store/sr9iyw9n2awaikjzvjfgwhvvn6vimf5w-pkg-config-0.29.2.drv
/nix/store/3zmkapnjbnajncxw7cix0lmj1fbspwa0-pkg-config-wrapper-0.29.2.drv
/nix/store/7dlkjg6cyd8d47qwiamxi77hld2z5360-mirrors-list.drv
/nix/store/i0zc5mm4vpj3lviyydb9s73j53mypkrg-nghttp2-1.49.0.drv
/nix/store/w37b5s734m53gxnzqyb5v0v98mhdfg2i-coreutils-9.1.drv
/nix/store/mmsrbggvzn6rwlx1ijw90sw3wvhzj18j-openssl-3.0.5.drv
/nix/store/n7iibs6b818v402j0yczf4mgy73sbzpv-libssh2-1.10.0.drv
/nix/store/z074ki54p77r7db3wsgxh9p18f67xnv8-curl-7.85.0.drv
/nix/store/hwymznwkd1kgf5ldcldjl9bnc1wz2azb-hello-2.12.1.tar.gz.drv
/nix/store/4a78f0s4p5h2sbcrrzayl5xas2i7zq1m-hello-2.12.1.drv</code></pre>
</details>
<p>The above command not only lists the build-time dependencies for the
<code>hello</code> package, but also their transitive build-time
dependencies. In other words, these are all the derivations needed to
build the <code>hello</code> package “from scratch” in the absence of
any cache products. We can see the complete tree of build-time
dependencies like this:</p>
<div class="sourceCode" id="cb16"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-store <span class="at">--query</span> <span class="at">--tree</span> <span class="st">"</span><span class="va">${DERIVATION}</span><span class="st">"</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/4a78f0s4p5h2sbcrrzayl5xas2i7zq1m-hello-2.12.1.drv</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a><span class="ex">├───/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh</span></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a><span class="ex">├───/nix/store/labgzlb16svs1z7z9a6f49b5zi8hb11s-bash-5.1-p16.drv</span></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> ├───/nix/store/7kcayxwk8khycxw1agmcyfm9vpsqpw4s-bootstrap-tools.drv</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/3glray2y14jpk1h6i599py7jdn3j2vns-mkdir.drv</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/50ql5q0raqkcydmpi6wqvnhs9hpdgg5f-cpio.drv</span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/81xahsrhpn9mbaslgi5sz7gsqra747d4-unpack-bootstrap-tools-<span class="op">></span></span>
<span id="cb16-9"><a href="#cb16-9" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/fzbk4fnbjqhr0l1scx5fspsx5najbrbm-bootstrap-tools.cpio.bz<span class="op">></span></span>
<span id="cb16-10"><a href="#cb16-10" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/gxzl4vmccqj89yh7kz62frkxzgdpkxmp-sh.drv</span>
<span id="cb16-11"><a href="#cb16-11" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ └───/nix/store/pjbpvdy0gais8nc4sj3kwpniq8mgkb42-bzip2.drv</span>
<span id="cb16-12"><a href="#cb16-12" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> ├───/nix/store/3lhw0v2wyzimzl96xfsk6psfmzh38irh-bash51-007.drv</span>
<span id="cb16-13"><a href="#cb16-13" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/7kcayxwk8khycxw1agmcyfm9vpsqpw4s-bootstrap-tools.drv [..<span class="op">></span></span>
<span id="cb16-14"><a href="#cb16-14" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/nbxwxwqwcr9rrmxb6gb532f18102815x-bootstrap-stage0-stdenv<span class="op">></span></span>
<span id="cb16-15"><a href="#cb16-15" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/1i5y55x4b4m9qkx5dqbmr1r6bvrqbanw-multiple-outputs.sh</span>
<span id="cb16-16"><a href="#cb16-16" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/59jmzisg8fkm9c125fw384dqq1np602l-move-docs.sh</span>
<span id="cb16-17"><a href="#cb16-17" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/7kcayxwk8khycxw1agmcyfm9vpsqpw4s-bootstrap-tools.drv<span class="op">></span></span>
<span id="cb16-18"><a href="#cb16-18" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/bnj8d7mvbkg3vdb07yz74yhl3g107qq5-patch-shebangs.sh</span>
<span id="cb16-19"><a href="#cb16-19" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/cickvswrvann041nqxb0rxilc46svw1n-prune-libtool-files<span class="op">></span></span>
<span id="cb16-20"><a href="#cb16-20" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/ckzrg0f0bdyx8rf703nc61r3hz5yys9q-builder.sh</span>
<span id="cb16-21"><a href="#cb16-21" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/fyaryjvghbkpfnsyw97hb3lyb37s1pd6-move-lib64.sh</span>
<span id="cb16-22"><a href="#cb16-22" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/g8xg0i02aqwhgxwd2vnp5ax3d6lrkg1v-strip.sh</span>
<span id="cb16-23"><a href="#cb16-23" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/jngr4r80x5jn482ckqrfh08ljrx1k86f-setup.sh</span>
<span id="cb16-24"><a href="#cb16-24" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/kd4xwxjpjxi71jkm6ka0np72if9rm3y0-move-sbin.sh</span>
<span id="cb16-25"><a href="#cb16-25" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/kxw6q8v6isaqjm702d71n2421cxamq68-make-symlinks-relat<span class="op">></span></span>
<span id="cb16-26"><a href="#cb16-26" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/m54bmrhj6fqz8nds5zcj97w9s9bckc9v-compress-man-pages.<span class="op">></span></span>
<span id="cb16-27"><a href="#cb16-27" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/ngg1cv31c8c7bcm2n8ww4g06nq7s4zhm-set-source-date-epo<span class="op">></span></span>
<span id="cb16-28"><a href="#cb16-28" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ └───/nix/store/wlwcf1nw2b21m4gghj70hbg1v7x53ld8-reproducible-builds<span class="op">></span></span>
<span id="cb16-29"><a href="#cb16-29" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ ├───/nix/store/i65va14cylqc74y80ksgnrsaixk39mmh-mirrors-list.drv</span>
<span id="cb16-30"><a href="#cb16-30" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/7kcayxwk8khycxw1agmcyfm9vpsqpw4s-bootstrap-tools.drv<span class="op">></span></span>
<span id="cb16-31"><a href="#cb16-31" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ ├───/nix/store/nbxwxwqwcr9rrmxb6gb532f18102815x-bootstrap-stage0-st<span class="op">></span></span>
<span id="cb16-32"><a href="#cb16-32" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ │ └───/nix/store/ycwm35msmsdi2qgjax1slmjffsmwy8am-write-mirror-list.sh</span>
<span id="cb16-33"><a href="#cb16-33" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> │ └───/nix/store/lphxcbw5wqsjskipaw1fb8lcf6pm6ri6-builder.sh</span>
<span id="cb16-34"><a href="#cb16-34" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span></code></pre></div>
<p>If we were to build and cache all of these build-time dependencies
then our local <code>/nix/store</code> and cache would explode in size.
Also, we do <em>not</em> need to do this because there is a better
solution …</p>
<h4 id="correct-solution">Correct solution</h4>
<p>The solution that provides the best value is to cache all transitive
build-time dependencies <em>that are present within the current
<code>/nix/store</code></em> after building the top-level build product.
In other words, don’t bother to predict which build-time dependencies we
need; instead, empirically infer which ones to cache based on which ones
Nix installed and used along the way.</p>
<p>This is not only more accurate, but it’s also more efficient: we
don’t need to build or download anything new because we’re only caching
things we already locally installed.</p>
<p>As a matter of fact, the <code>nix-store</code> command already
supports this use case quite well. If you consult the documentation for
the <code>--requisites</code> flag, you’ll find this gem:</p>
<pre><code> • --requisites; -R
Prints out the closure (../glossary.md) of the store path paths.
This query has one option:
• --include-outputs Also include the existing output paths of store
derivations, and their closures.
This query can be used to implement various kinds of deployment. A
source deployment is obtained by distributing the closure of a store
derivation. A binary deployment is obtained by distributing the closure
of an output path. A cache deployment (combined source/binary
deployment, including binaries of build-time-only dependencies) is
obtained by distributing the closure of a store derivation and
specifying the option --include-outputs.</code></pre>
<p>We’re specifically interested in a “cache deployment”, so we’re going
to do exactly what the documentation says and use the
<code>--include-outputs</code> flag in conjunction with the
<code>--requisites</code> flag. In other words, the
<code>--include-outputs</code> flag was expressly created for this use
case!</p>
<p>So here is the simplest, but least robust, version of the script for
computing the set of build-time dependencies to cache, as a Bash
array:</p>
<div class="sourceCode" id="cb18"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> <span class="co"># Continue reading before using this code; there's a more robust version later</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> <span class="co"># Optional: Perform the build if you haven't already</span></span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build <span class="st">"</span><span class="va">${BUILD}</span><span class="st">"</span></span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-6"><a href="#cb18-6" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> DERIVATION=<span class="st">"</span><span class="va">$(</span><span class="ex">nix</span> path-info <span class="at">--derivation</span> <span class="st">"</span><span class="va">${BUILD}</span><span class="st">"</span><span class="va">)</span><span class="st">"</span></span>
<span id="cb18-7"><a href="#cb18-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-8"><a href="#cb18-8" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> DEPENDENCIES=<span class="er">(</span><span class="va">$(</span><span class="ex">nix-store</span> <span class="at">--query</span> <span class="at">--requisites</span> <span class="at">--include-outputs</span> <span class="st">"</span><span class="va">${DERIVATION}</span><span class="st">"</span><span class="va">)</span><span class="kw">)</span></span>
<span id="cb18-9"><a href="#cb18-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-10"><a href="#cb18-10" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix store sign <span class="at">--key-file</span> <span class="st">"</span><span class="va">${KEY_FILE}</span><span class="st">"</span> <span class="at">--recursive</span> <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span></span>
<span id="cb18-11"><a href="#cb18-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-12"><a href="#cb18-12" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix copy <span class="at">--to</span> <span class="st">"</span><span class="va">${CACHE}</span><span class="st">"</span> <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span></span></code></pre></div>
<p>The above code is simple and clear enough to illustrate the idea, but
we’re going to make a few adjustments to make this code more robust.</p>
<p>Specifically, we’re going to:</p>
<ul>
<li><p>Change the code to support an array of build targets</p>
<p>i.e. <code>BUILDS</code> instead of <code>BUILD</code></p></li>
<li><p>Use <code>mapfile</code> instead of <code>($(…))</code> to create
intermediate arrays</p>
<p>See: <a
href="https://www.shellcheck.net/wiki/SC2207"><code>SC2207</code></a></p></li>
<li><p>Use <code>xargs</code> to handle command line length
limits</p></li>
</ul>
<p>… which gives us:</p>
<div class="sourceCode" id="cb19"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> <span class="co"># Optional: Perform the build if you haven't already</span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="st">"</span><span class="va">${BUILDS</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">xargs</span> nix build</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> mapfile <span class="at">-t</span> DERIVATIONS <span class="op"><</span> <span class="op"><(</span><span class="bu">echo</span> <span class="st">"</span><span class="va">${BUILDS</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">xargs</span> nix path-info <span class="at">--derivation</span><span class="op">)</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> mapfile <span class="at">-t</span> DEPENDENCIES <span class="op"><</span> <span class="op"><(</span><span class="bu">echo</span> <span class="st">"</span><span class="va">${DERIVATIONS</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">xargs</span> nix-store <span class="at">--query</span> <span class="at">--requisites</span> <span class="at">--include-outputs</span><span class="op">)</span></span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-8"><a href="#cb19-8" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">xargs</span> nix store sign <span class="at">--key-file</span> <span class="st">"</span><span class="va">${KEY_FILE}</span><span class="st">"</span> <span class="at">--recursive</span></span>
<span id="cb19-9"><a href="#cb19-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-10"><a href="#cb19-10" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="st">"</span><span class="va">${DEPENDENCIES</span><span class="op">[@]</span><span class="va">}</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">xargs</span> nix copy <span class="at">--to</span> <span class="st">"</span><span class="va">${CACHE}</span><span class="st">"</span></span></code></pre></div>
<p>… where you:</p>
<ul>
<li><p>replace <code>BUILDS</code> with a Bash array containing what you
want to build</p>
<p>e.g. <code>.#example</code> or <code>nixpkgs#hello</code></p></li>
<li><p>replace <code>CACHE</code> with whatever store you use as your
cache</p>
<p>e.g. <code>s3://cache.example.com</code></p></li>
<li><p>replace <code>KEY_FILE</code> with the path to your cache signing
key</p></li>
</ul>
<h4 id="conclusion">Conclusion</h4>
<p>That last script is the pedantically robust way to do this in Bash if
you want to be super paranoid. The above script might not work in other
shells, but hopefully this post was sufficiently clear that you can
adapt the script to your needs.</p>
<p>If I made any mistakes in the above post, let me know and I can fix
them.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-58339848719453911832022-10-20T07:31:00.008-07:002022-10-22T13:12:19.341-07:00What does "isomorphic" mean (in Haskell)?<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@GabriellaG439">
<meta name="twitter:title" content="What does "isomorphic" mean (in Haskell)">
<meta name="twitter:description" content="An explanation of isomorphisms in the context of the Haskell programming language">
<title>What does "isomorphic" mean (in Haskell)</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { color: #008000; } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { color: #008000; font-weight: bold; } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>Sometimes you’ll hear someone describe two things as being
“isomorphic” to one another and I wanted to explain what that means.</p>
<p>You might have already guessed that “isomorphic” is a synonym for
“equivalent”, and that would have been a pretty good guess. Really, the
main difference between the two words is that “isomorphic” has a more
precise and more general definition than “equivalent”.</p>
<p>In this post I will introduce a more precise definition of
“isomorphic”, using Haskell code. This definition won’t be the fully
general definition, but I still hope to give you some taste of how
“isomorphic” can denote something more than just “equivalent”.</p>
<h4 id="the-simple-version">The simple version</h4>
<p>The simplest and least general definition of “isomorphic” (in
Haskell) is:</p>
<blockquote>
<p>Two types, <code>A</code>, and <code>B</code>, are isomorphic if
there exist two functions, <code>forward</code> and
<code>backward</code> of the following types:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">forward ::</span> <span class="dt">A</span> <span class="ot">-></span> <span class="dt">B</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ot">backward ::</span> <span class="dt">B</span> <span class="ot">-></span> <span class="dt">A</span></span></code></pre></div>
<p>… such that the following two equations (which I will refer to as the “isomorphism
laws”) are true:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>forward <span class="op">.</span> backward <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>backward <span class="op">.</span> forward <span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
</blockquote>
<p><code>id</code> here is the identity function from Haskell’s Prelude,
defined like this:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span><span class="ot"> ::</span> a <span class="ot">-></span> a</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span> x <span class="ot">=</span> x</span></code></pre></div>
<p>… and <code>(.)</code> is the function composition operator (also
from Haskell’s Prelude), defined like this:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ot">(.) ::</span> (b <span class="ot">-></span> c) <span class="ot">-></span> (a <span class="ot">-></span> b) <span class="ot">-></span> (a <span class="ot">-></span> c)</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>(f <span class="op">.</span> g) x <span class="ot">=</span> f (g x)</span></code></pre></div>
<p>According to the above definition, the types
<code>Bool -> a</code> and <code>(a, a)</code> are isomorphic,
because we can define two functions:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ot">forward ::</span> (<span class="dt">Bool</span> <span class="ot">-></span> a) <span class="ot">-></span> (a, a)</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>forward function <span class="ot">=</span> (function <span class="dt">False</span>, function <span class="dt">True</span>)</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="ot">backward ::</span> (a, a) <span class="ot">-></span> (<span class="dt">Bool</span> <span class="ot">-></span> a)</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>backward (first, second) <span class="dt">False</span> <span class="ot">=</span> first</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>backward (first, second) <span class="dt">True</span> <span class="ot">=</span> second</span></code></pre></div>
<p>… and we can prove that those two functions satisfy the isomorphism
laws using <a
href="https://www.haskellforall.com/2013/12/equational-reasoning.html">equational
reasoning</a>.</p>
<details>
<summary>
Proof of the isomorphism laws (click to expand)
</summary>
<p>Here’s the proof of the first isomorphism law:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>forward <span class="op">.</span> backward</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- (f . g) = \x -> f (g x)</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- … where:</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- f = forward</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- g = backward</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \x <span class="ot">-></span> forward (backward x)</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co">-- x = (first, second)</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \(first, second) <span class="ot">-></span> forward (backward (first, second))</span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co">-- forward function = (function False, function True)</span></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \(first, second) <span class="ot">-></span></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a> (backward (first, second) <span class="dt">False</span>, backward (first, second) <span class="dt">True</span>)</span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a><span class="co">-- backward (first, second) False = first</span></span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a><span class="co">-- backward (first, second) True = second</span></span>
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \(first, second) <span class="ot">-></span> (first, second)</span>
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="co">-- x = (first, second)</span></span>
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a><span class="co">-- … in reverse</span></span>
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \x <span class="ot">-></span> x</span>
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a><span class="co">-- id x = x</span></span>
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a><span class="co">-- … in reverse</span></span>
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \x <span class="ot">-></span> <span class="fu">id</span> x</span>
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a><span class="co">-- η-reduction</span></span>
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
<p>… and here is the proof of the second isomorphism law:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>backward <span class="op">.</span> forward</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- (f . g) = \x -> f (g x)</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- … where:</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- f = backward</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- g = forward</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- x = function</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \function <span class="ot">-></span> backward (forward function)</span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- forward function = (function False, function True)</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \function <span class="ot">-></span> backward (function <span class="dt">False</span>, function <span class="dt">True</span>)</span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a><span class="co">-- η-expand</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> \function bool <span class="ot">-></span> backward (function <span class="dt">False</span>, function <span class="dt">True</span>) bool</span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a><span class="co">-- There are two possible cases:</span></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a><span class="co">-- Case #0: bool = False</span></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a><span class="co">-- Case #1: bool = True</span></span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a><span class="co">-- Proof for case #0: bool = False</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function bool <span class="ot">-></span> backward (function <span class="dt">False</span>, function <span class="dt">True</span>) <span class="dt">False</span></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a> <span class="co">-- backward (first, second) False = first</span></span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … where:</span></span>
<span id="cb7-29"><a href="#cb7-29" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-30"><a href="#cb7-30" aria-hidden="true" tabindex="-1"></a> <span class="co">-- first = function False</span></span>
<span id="cb7-31"><a href="#cb7-31" aria-hidden="true" tabindex="-1"></a> <span class="co">-- second = function True</span></span>
<span id="cb7-32"><a href="#cb7-32" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function bool <span class="ot">-></span> function <span class="dt">False</span></span>
<span id="cb7-33"><a href="#cb7-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-34"><a href="#cb7-34" aria-hidden="true" tabindex="-1"></a> <span class="co">-- bool = False</span></span>
<span id="cb7-35"><a href="#cb7-35" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-36"><a href="#cb7-36" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … in reverse</span></span>
<span id="cb7-37"><a href="#cb7-37" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function bool <span class="ot">-></span> function bool</span>
<span id="cb7-38"><a href="#cb7-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-39"><a href="#cb7-39" aria-hidden="true" tabindex="-1"></a> <span class="co">-- η-reduction</span></span>
<span id="cb7-40"><a href="#cb7-40" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function <span class="ot">-></span> function</span>
<span id="cb7-41"><a href="#cb7-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-42"><a href="#cb7-42" aria-hidden="true" tabindex="-1"></a> <span class="co">-- id x = x</span></span>
<span id="cb7-43"><a href="#cb7-43" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-44"><a href="#cb7-44" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … in reverse</span></span>
<span id="cb7-45"><a href="#cb7-45" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function <span class="ot">-></span> <span class="fu">id</span> function</span>
<span id="cb7-46"><a href="#cb7-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-47"><a href="#cb7-47" aria-hidden="true" tabindex="-1"></a> <span class="co">-- η-reduction</span></span>
<span id="cb7-48"><a href="#cb7-48" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb7-49"><a href="#cb7-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-50"><a href="#cb7-50" aria-hidden="true" tabindex="-1"></a><span class="co">-- Proof for case #1: bool = True</span></span>
<span id="cb7-51"><a href="#cb7-51" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function bool <span class="ot">-></span> backward (function <span class="dt">False</span>, function <span class="dt">True</span>) <span class="dt">True</span></span>
<span id="cb7-52"><a href="#cb7-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-53"><a href="#cb7-53" aria-hidden="true" tabindex="-1"></a> <span class="co">-- backward (first, second) True = second</span></span>
<span id="cb7-54"><a href="#cb7-54" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-55"><a href="#cb7-55" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … where:</span></span>
<span id="cb7-56"><a href="#cb7-56" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-57"><a href="#cb7-57" aria-hidden="true" tabindex="-1"></a> <span class="co">-- first = function False</span></span>
<span id="cb7-58"><a href="#cb7-58" aria-hidden="true" tabindex="-1"></a> <span class="co">-- second = function True</span></span>
<span id="cb7-59"><a href="#cb7-59" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function bool <span class="ot">-></span> function <span class="dt">True</span></span>
<span id="cb7-60"><a href="#cb7-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-61"><a href="#cb7-61" aria-hidden="true" tabindex="-1"></a> <span class="co">-- b = True</span></span>
<span id="cb7-62"><a href="#cb7-62" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-63"><a href="#cb7-63" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … in reverse</span></span>
<span id="cb7-64"><a href="#cb7-64" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function bool <span class="ot">-></span> function bool</span>
<span id="cb7-65"><a href="#cb7-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-66"><a href="#cb7-66" aria-hidden="true" tabindex="-1"></a> <span class="co">-- η-reduction</span></span>
<span id="cb7-67"><a href="#cb7-67" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function <span class="ot">-></span> function</span>
<span id="cb7-68"><a href="#cb7-68" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-69"><a href="#cb7-69" aria-hidden="true" tabindex="-1"></a> <span class="co">-- id x = x</span></span>
<span id="cb7-70"><a href="#cb7-70" aria-hidden="true" tabindex="-1"></a> <span class="co">--</span></span>
<span id="cb7-71"><a href="#cb7-71" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … in reverse</span></span>
<span id="cb7-72"><a href="#cb7-72" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \function <span class="ot">-></span> <span class="fu">id</span> function</span>
<span id="cb7-73"><a href="#cb7-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-74"><a href="#cb7-74" aria-hidden="true" tabindex="-1"></a> <span class="co">-- η-reduction</span></span>
<span id="cb7-75"><a href="#cb7-75" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
</details>
<p>We’ll use the notation <code>A ≅ B</code> as a short-hand for
“<code>A</code> is isomorphic to <code>B</code>”, so we can also
write:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Bool</span> <span class="ot">-></span> a ≅ (a, a)</span></code></pre></div>
<p>Whenever we declare that two types are isomorphic we need to actually
specify what the <code>forward</code> and <code>backward</code>
conversion functions are and prove that they satisfy isomorphism laws.
The existence of <code>forward</code> and <code>backward</code>
functions of the correct input and output types is not enough to
establish that the two types are isomorphic.</p>
<p>For example, suppose we changed the definition of
<code>forward</code> to:</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ot">forward ::</span> (<span class="dt">Bool</span> <span class="ot">-></span> a) <span class="ot">-></span> (a, a)</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>forward function <span class="ot">=</span> (function <span class="dt">True</span>, function <span class="dt">False</span>)</span></code></pre></div>
<p>Then <code>forward . backward</code> and
<code>backward . forward</code> would still type-check and have the
right type, but they would no longer be equal to <code>id</code>.</p>
<p>In other words, when discussing isomorphic types, it’s technically
not enough that the two types are equivalent. The <em>way</em> in which
they are equivalent matters, too, if we want to be pedantic. In
practice, though, if there’s only one way to implement the two
conversion functions then people won’t bother to explicitly specify
them.</p>
<p>The reason why this is important is because an isomorphism also gives
us an explicit way to convert between the two types. We're not just declaring
that they're equivalent, but we're spelling out exactly how to transform
each type into the other type, which is very useful!</p>
<h4 id="more-examples">More examples</h4>
<p>Let’s speedrun through a few more examples of isomorphic types, which
all parallel the rules of arithmetic:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- 0 + a = a</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Either</span> <span class="dt">Void</span> a ≅ a</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- a + (b + c) = (a + b) + c</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="dt">Either</span> a (<span class="dt">Either</span> b c) <span class="ot">=</span> <span class="dt">Either</span> (<span class="dt">Either</span> a b) c</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- 1 × a = a</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>((), a) ≅ a</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- a × (b × c) = (a × b) × c</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a>(a, (b, c)) ≅ ((a, b), c)</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- 0 × a = 0</span></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a>(<span class="dt">Void</span>, a) ≅ <span class="dt">Void</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a><span class="co">-- a × (b + c) = (a × b) + (a × c)</span></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a>(a, <span class="dt">Either</span> b c) ≅ <span class="dt">Either</span> (a, b) (a, c)</span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a><span class="co">-- a ^ 1 = a</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a>() <span class="ot">-></span> a ≅ a</span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a><span class="co">-- a ^ 0 = 1</span></span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a><span class="dt">Void</span> <span class="ot">-></span> a ≅ ()</span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a><span class="co">-- (c ^ b) ^ a = (c ^ a) ^ b</span></span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a>a <span class="ot">-></span> b <span class="ot">-></span> c ≅ b <span class="ot">-></span> a <span class="ot">-></span> c</span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a><span class="co">-- (c ^ b) ^ a = c ^ (a × b)</span></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a>a <span class="ot">-></span> b <span class="ot">-></span> c ≅ (a, b) <span class="ot">-></span> c</span></code></pre></div>
<p><strong>Exercise:</strong> implement the <code>forward</code> and
<code>backward</code> functions for some of the above types and prove
the isomorphism laws for each pair of functions. It will probably be
very tedious to prove all of the above examples, so pick the ones that
interest you the most.</p>
<h4 id="intermediate-tricks">Intermediate tricks</h4>
<p>This section will introduce some more advanced tricks for proving
that two types are isomorphic.</p>
<p>First, let’s start with a few ground rules for working with all
isomorphisms:</p>
<ul>
<li><p>Reflexivity: <code>a ≅ a</code></p></li>
<li><p>Symmetry: If <code>a ≅ b</code> then <code>b ≅ a</code></p></li>
<li><p>Transitivity: If <code>a ≅ b</code> and <code>b ≅ c</code> then
<code>a ≅ c</code></p></li>
</ul>
<p>Now let’s get into some Haskell-specific rules:</p>
<blockquote>
<p>a <code>newtype</code> in Haskell is isomorphic to the underlying
type if the <code>newtype</code> constructor is public.</p>
</blockquote>
<p>For example, if we were to define:</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Name</span> <span class="ot">=</span> <span class="dt">Name</span> {<span class="ot"> getName ::</span> <span class="dt">String</span> }</span></code></pre></div>
<p>… then <code>Name</code> and <code>String</code> would be isomorphic
(<code>Name ≅ String</code>), where:</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ot">forward ::</span> <span class="dt">Name</span> <span class="ot">-></span> <span class="dt">String</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>forward <span class="ot">=</span> getName</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="ot">backward ::</span> <span class="dt">String</span> <span class="ot">-></span> <span class="dt">Name</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>backward <span class="ot">=</span> <span class="dt">Name</span></span></code></pre></div>
<p>One such <code>newtype</code> that shows up pretty often when
reasoning about isomorphic types is the <a
href="https://hackage.haskell.org/package/base/docs/Data-Functor-Identity.html#t:Identity"><code>Identity</code></a>
type constructor from <a
href="https://hackage.haskell.org/package/base/docs/Data-Functor-Identity.html"><code>Data.Functor.Identity</code></a>:</p>
<div class="sourceCode" id="cb13"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Identity</span> a <span class="ot">=</span> <span class="dt">Identity</span> {<span class="ot"> runIdentity ::</span> a }</span></code></pre></div>
<p>… where <code>Identity a ≅ a</code>.</p>
<p>To see why <code>Identity</code> is useful, consider the following
two types:</p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">State</span> s a <span class="ot">=</span> <span class="dt">State</span> {<span class="ot"> runState ::</span> s <span class="ot">-></span> (a, s) }</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">StateT</span> s m a <span class="ot">=</span> <span class="dt">StateT</span> {<span class="ot"> runStateT ::</span> s <span class="ot">-></span> m (a, s) }</span></code></pre></div>
<p>The latter <code>newtype</code> is from the <code>transformers</code>
package, which is how we layer on the “state” effect within a monad
transformer stack. If you don’t understand what that means, that’s okay;
it’s not that relevant to the point.</p>
<p>However, the <code>transformers</code> package doesn’t define
<code>State</code> as above. Instead, the <code>transformers</code>
package defines <code>State</code> like this:</p>
<div class="sourceCode" id="cb15"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">State</span> s <span class="ot">=</span> <span class="dt">StateT</span> s <span class="dt">Identity</span></span></code></pre></div>
<p>The latter <code>type</code> synonym definition for
<code>State</code> is equivalent (“isomorphic”) to the
<code>newtype</code> definition for <code>State</code> I provided above.
In order to prove that though I’ll need to distinguish between the two
<code>State</code> type constructors, so I’ll use a numeric subscript to
distinguish them:</p>
<div class="sourceCode" id="cb16"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Functor.Identity</span> (<span class="dt">Identity</span>)</span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">State</span>₀ s a <span class="ot">=</span> <span class="dt">State</span>₀ {<span class="ot"> runState ::</span> s <span class="ot">-></span> (a, s) }</span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">StateT</span> s m a <span class="ot">=</span> <span class="dt">StateT</span> {<span class="ot"> runStateT ::</span> s <span class="ot">-></span> m (a, s) }</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">State</span>₁ s <span class="ot">=</span> <span class="dt">StateT</span> s <span class="dt">Identity</span></span></code></pre></div>
<p>… and then we can prove that <code>State₀</code> is isomorphic to
<code>State₁</code> like this:</p>
<ul>
<li><p><code>State₀ s a ≅ s -> (a, s)</code></p>
<p>… because the <code>State₀</code> <code>newtype</code> is isomorphic
to the underlying type</p></li>
<li><p><code>s -> (a, s) ≅ s -> Identity (a, s)</code></p>
<p>… because the <code>Identity</code> newtype is isomorphic to the
underlying type</p></li>
<li><p><code>s -> Identity (a, s) ≅ StateT s Identity a</code></p>
<p>… because the <code>StateT</code> newtype is isomorphic to the
underlying type</p></li>
<li><p><code>StateT s Identity a = State₁ s a</code></p>
<p>… because of how the <code>State₁</code> type synonym is
defined.</p></li>
</ul>
<p>Therefore, by transitivity, we can conclude:</p>
<ul>
<li><code>State₀ s a ≅ State₁ s a</code></li>
</ul>
<p>Okay, now let’s introduce an <strong>extremely useful</strong> rule
related to isomorphic types:</p>
<blockquote>
<p>If <code>f</code> is a <code>Functor</code> then
<code>forall r . (a -> r) -> f r</code> is isomorphic to
<code>f a</code>.</p>
</blockquote>
<p>Or in other words:</p>
<div class="sourceCode" id="cb17"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Functor</span> f <span class="ot">=></span> (<span class="kw">forall</span> r <span class="op">.</span> (a <span class="ot">-></span> r) <span class="ot">-></span> f r) ≅ f a</span></code></pre></div>
<p>… and here are the two conversion functions:</p>
<div class="sourceCode" id="cb18"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE RankNTypes #-}</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a><span class="ot">forward ::</span> <span class="dt">Functor</span> f <span class="ot">=></span> (<span class="kw">forall</span> r <span class="op">.</span> (a <span class="ot">-></span> r) <span class="ot">-></span> f r) <span class="ot">-></span> f a</span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a>forward f <span class="ot">=</span> f <span class="fu">id</span></span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-6"><a href="#cb18-6" aria-hidden="true" tabindex="-1"></a><span class="ot">backward ::</span> <span class="dt">Functor</span> f <span class="ot">=></span> f a <span class="ot">-></span> (<span class="kw">forall</span> r <span class="op">.</span> (a <span class="ot">-></span> r) <span class="ot">-></span> f r)</span>
<span id="cb18-7"><a href="#cb18-7" aria-hidden="true" tabindex="-1"></a>backward fa k <span class="ot">=</span> <span class="fu">fmap</span> k fa</span></code></pre></div>
<p>This is essentially the <a
href="https://en.wikipedia.org/wiki/Yoneda_lemma">Yoneda lemma</a> in
Haskell form, which is actually a bit tricky to prove. If you don’t
believe me, try proving the isomorphism laws for the above
<code>forward</code> and <code>backward</code> functions and see how far
you get. It’s much easier to rely on the fact that someone else already
did the hard work of proving those isomorphism laws for us.</p>
<p>Here’s a concrete example of the Yoneda lemma in action. Suppose that
I want to prove that there is only one implementation of the identity
function, <code>id</code>. I can do so by proving that the type of the
identity function (<code>forall a . a -> a</code>) is isomorphic to
the <code>()</code> type (a type inhabited by exactly one value):</p>
<div class="sourceCode" id="cb19"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a>(<span class="kw">forall</span> a <span class="op">.</span> a <span class="ot">-></span> a) ≅ ()</span></code></pre></div>
<p>Here’s how you prove that by chaining together several isomorphic
types:</p>
<div class="sourceCode" id="cb20"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a> (<span class="kw">forall</span> a <span class="op">.</span> a <span class="ot">-></span> a)</span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a> <span class="co">-- a ≅ () -> a</span></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> a <span class="op">.</span> (() <span class="ot">-></span> a) <span class="ot">-></span> a)</span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="co">-- a ≅ Identity a</span></span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> a <span class="op">.</span> (() <span class="ot">-></span> a) <span class="ot">-></span> <span class="dt">Identity</span> a)</span>
<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ✨ Yoneda lemma (where f = Identity) ✨</span></span>
<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a>≅ <span class="dt">Identity</span> ()</span>
<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a>≅ ()</span></code></pre></div>
<p>… so since the <code>()</code> type is inhabited by exactly one value
(the <code>()</code> term) and the <code>()</code> type is isomorphic to
the type of <code>id</code>, then there is exactly one way to implement
<code>id</code> (which is <code>id x = x</code>).</p>
<blockquote>
<p>Note: To be totally pedantic, there is exactly one way to implement
<code>id</code> “up to isomorphism”. This is how we say that there might
be several syntactically different ways of implementing <code>id</code>,
such as:</p>
<div class="sourceCode" id="cb21"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span> x <span class="ot">=</span> x</span>
<span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-3"><a href="#cb21-3" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span> y <span class="ot">=</span> y</span>
<span id="cb21-4"><a href="#cb21-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-5"><a href="#cb21-5" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span> <span class="ot">=</span> \x <span class="ot">-></span> x</span>
<span id="cb21-6"><a href="#cb21-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-7"><a href="#cb21-7" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span> x <span class="ot">=</span> y</span>
<span id="cb21-8"><a href="#cb21-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb21-9"><a href="#cb21-9" aria-hidden="true" tabindex="-1"></a> y <span class="ot">=</span> x</span></code></pre></div>
<p>… but all of those ways of implementing <code>id</code> are
isomorphic to one another (in a slightly different sense that I have not
covered), so there is essentially only one way of implementing
<code>id.</code></p>
</blockquote>
<p>Similarly, we can prove that there are exactly two ways to implement
a function of type <code>forall a . a -> a -> a</code> by showing
that such a type is isomorphic to <code>Bool</code> (a type inhabited by
exactly two values):</p>
<div class="sourceCode" id="cb22"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a> (<span class="kw">forall</span> a <span class="op">.</span> a <span class="ot">-></span> a <span class="ot">-></span> a)</span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a> <span class="co">-- a -> b -> c ≅ (a, b) -> c</span></span>
<span id="cb22-3"><a href="#cb22-3" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> a <span class="op">.</span> (a, a) <span class="ot">-></span> a)</span>
<span id="cb22-4"><a href="#cb22-4" aria-hidden="true" tabindex="-1"></a> <span class="co">-- (a, a) ≅ Bool -> a</span></span>
<span id="cb22-5"><a href="#cb22-5" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> a <span class="op">.</span> (<span class="dt">Bool</span> <span class="ot">-></span> a) <span class="ot">-></span> a)</span>
<span id="cb22-6"><a href="#cb22-6" aria-hidden="true" tabindex="-1"></a> <span class="co">-- a ≅ Identity a</span></span>
<span id="cb22-7"><a href="#cb22-7" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> a <span class="op">.</span> (<span class="dt">Bool</span> <span class="ot">-></span> a) <span class="ot">-></span> <span class="dt">Identity</span> a)</span>
<span id="cb22-8"><a href="#cb22-8" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ✨ Yoneda lemma (where f = Identity) ✨</span></span>
<span id="cb22-9"><a href="#cb22-9" aria-hidden="true" tabindex="-1"></a>≅ <span class="dt">Identity</span> <span class="dt">Bool</span></span>
<span id="cb22-10"><a href="#cb22-10" aria-hidden="true" tabindex="-1"></a>≅ <span class="dt">Bool</span></span></code></pre></div>
<p>… and in case you’re curious, here are the only two possible ways to
implement that type (up to isomorphism):</p>
<div class="sourceCode" id="cb23"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE ExplicitForAll #-}</span></span>
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="ot">false ::</span> <span class="kw">forall</span> a <span class="op">.</span> a <span class="ot">-></span> a <span class="ot">-></span> a</span>
<span id="cb23-4"><a href="#cb23-4" aria-hidden="true" tabindex="-1"></a>false f t <span class="ot">=</span> f</span>
<span id="cb23-5"><a href="#cb23-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-6"><a href="#cb23-6" aria-hidden="true" tabindex="-1"></a><span class="ot">true ::</span> <span class="kw">forall</span> a <span class="op">.</span> a <span class="ot">-></span> a <span class="ot">-></span> a</span>
<span id="cb23-7"><a href="#cb23-7" aria-hidden="true" tabindex="-1"></a>true f t <span class="ot">=</span> t</span></code></pre></div>
<p>Here’s one last example of using the Yoneda lemma to prove that:</p>
<div class="sourceCode" id="cb24"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a>(<span class="kw">forall</span> r <span class="op">.</span> (a <span class="ot">-></span> r) <span class="ot">-></span> r) ≅ a</span></code></pre></div>
<p>… which you can prove like this:</p>
<div class="sourceCode" id="cb25"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a> (<span class="kw">forall</span> r <span class="op">.</span> (a <span class="ot">-></span> r) <span class="ot">-></span> r)</span>
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Identity r ≅ r</span></span>
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> r <span class="op">.</span> (a <span class="ot">-></span> r) <span class="ot">-></span> <span class="dt">Identity</span> r)</span>
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ✨ Yoneda lemma (where f = Identity) ✨</span></span>
<span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a>≅ <span class="dt">Identity</span> a</span>
<span id="cb25-6"><a href="#cb25-6" aria-hidden="true" tabindex="-1"></a>≅ a</span></code></pre></div>
<p><strong>Exercise:</strong> Prove that these two types are
isomorphic:</p>
<div class="sourceCode" id="cb26"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a>(<span class="kw">forall</span> r <span class="op">.</span> (b <span class="ot">-></span> r) <span class="ot">-></span> (a <span class="ot">-></span> r)) ≅ a <span class="ot">-></span> b</span></code></pre></div>
<details>
<summary>
Solution (click to expand)
</summary>
<div class="sourceCode" id="cb27"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a> (<span class="kw">forall</span> r <span class="op">.</span> (b <span class="ot">-></span> r) <span class="ot">-></span> (a <span class="ot">-></span> r))</span>
<span id="cb27-2"><a href="#cb27-2" aria-hidden="true" tabindex="-1"></a> <span class="co">-- a -> b -> c ≅ b -> a -> c</span></span>
<span id="cb27-3"><a href="#cb27-3" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> r <span class="op">.</span> a <span class="ot">-></span> (b <span class="ot">-></span> r) <span class="ot">-></span> r)</span>
<span id="cb27-4"><a href="#cb27-4" aria-hidden="true" tabindex="-1"></a> <span class="co">-- r ≅ Identity r</span></span>
<span id="cb27-5"><a href="#cb27-5" aria-hidden="true" tabindex="-1"></a>≅ (<span class="kw">forall</span> r <span class="op">.</span> a <span class="ot">-></span> (b <span class="ot">-></span> r) <span class="ot">-></span> <span class="dt">Identity</span> r)</span>
<span id="cb27-6"><a href="#cb27-6" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ✨ Yoneda lemma (where f = Identity) ✨</span></span>
<span id="cb27-7"><a href="#cb27-7" aria-hidden="true" tabindex="-1"></a>≅ a <span class="ot">-></span> <span class="dt">Identity</span> b</span>
<span id="cb27-8"><a href="#cb27-8" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Identity b ≅ b</span></span>
<span id="cb27-9"><a href="#cb27-9" aria-hidden="true" tabindex="-1"></a>≅ a <span class="ot">-></span> b</span></code></pre></div>
</details>
<h4 id="isomorphism">Isomorphism</h4>
<p>So far we’ve only used the word “isomorphic” but there is a related
word we should cover: “isomorphism”.</p>
<p>In Haskell, if the types <code>A</code> and <code>B</code> are
“isomorphic” then an “isomorphism” between them is the corresponding
pair of functions converting between them (i.e. <code>forward</code> and
<code>backward</code>).</p>
<p>The easiest way to explain this is to actually define an isomorphism
type in Haskell:</p>
<div class="sourceCode" id="cb28"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Isomorphism</span> a b <span class="ot">=</span> <span class="dt">Isomorphism</span></span>
<span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> forward ::</span> a <span class="ot">-></span> b</span>
<span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> backward ::</span> b <span class="ot">-></span> a</span>
<span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>For example:</p>
<div class="sourceCode" id="cb29"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="ot">exampleIsomorphism ::</span> <span class="dt">Isomorphism</span> ((a, b) <span class="ot">-></span> c) (a <span class="ot">-></span> b <span class="ot">-></span> c)</span>
<span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a>exampleIsomorphism <span class="ot">=</span> <span class="dt">Isomorphism</span>{ forward <span class="ot">=</span> <span class="fu">curry</span>, backward <span class="ot">=</span> <span class="fu">uncurry</span> }</span></code></pre></div>
<p>However, this is not the only way we can encode an isomorphism in
Haskell. For example, the <code>lens</code> package has an
<code>Iso</code> type which can also represent an isomorphism:</p>
<div class="sourceCode" id="cb30"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Lens</span> (<span class="dt">Iso'</span>, iso)</span>
<span id="cb30-2"><a href="#cb30-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb30-3"><a href="#cb30-3" aria-hidden="true" tabindex="-1"></a><span class="ot">exampleIso ::</span> <span class="dt">Iso'</span> ((a, b) <span class="ot">-></span> c) (a <span class="ot">-></span> b <span class="ot">-></span> c)</span>
<span id="cb30-4"><a href="#cb30-4" aria-hidden="true" tabindex="-1"></a>exampleIso <span class="ot">=</span> iso <span class="fu">curry</span> <span class="fu">uncurry</span></span></code></pre></div>
<p>These two types are equivalent. In fact, you might even say they are
… isomorphic 👀.</p>
<div class="sourceCode" id="cb31"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb31-1"><a href="#cb31-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE NamedFieldPuns #-}</span></span>
<span id="cb31-2"><a href="#cb31-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb31-3"><a href="#cb31-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Lens</span> (<span class="dt">AnIso'</span>, <span class="dt">Iso'</span>, cloneIso, iso, review, view)</span>
<span id="cb31-4"><a href="#cb31-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb31-5"><a href="#cb31-5" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Isomorphism</span> a b <span class="ot">=</span> <span class="dt">Isomorphism</span></span>
<span id="cb31-6"><a href="#cb31-6" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> forward ::</span> a <span class="ot">-></span> b</span>
<span id="cb31-7"><a href="#cb31-7" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> backward ::</span> b <span class="ot">-></span> a</span>
<span id="cb31-8"><a href="#cb31-8" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb31-9"><a href="#cb31-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb31-10"><a href="#cb31-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- | We have to use `AnIso'` here instead of `Iso'` for reasons I won't go into</span></span>
<span id="cb31-11"><a href="#cb31-11" aria-hidden="true" tabindex="-1"></a><span class="ot">isomorphismIsomorphism ::</span> <span class="dt">Isomorphism</span> (<span class="dt">Isomorphism</span> a b) (<span class="dt">AnIso'</span> a b)</span>
<span id="cb31-12"><a href="#cb31-12" aria-hidden="true" tabindex="-1"></a>isomorphismIsomorphism <span class="ot">=</span> <span class="dt">Isomorphism</span>{ forward, backward }</span>
<span id="cb31-13"><a href="#cb31-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb31-14"><a href="#cb31-14" aria-hidden="true" tabindex="-1"></a><span class="ot"> forward ::</span> <span class="dt">Isomorphism</span> a b <span class="ot">-></span> <span class="dt">AnIso'</span> a b</span>
<span id="cb31-15"><a href="#cb31-15" aria-hidden="true" tabindex="-1"></a> forward (<span class="dt">Isomorphism</span> f b) <span class="ot">=</span> iso f b</span>
<span id="cb31-16"><a href="#cb31-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb31-17"><a href="#cb31-17" aria-hidden="true" tabindex="-1"></a><span class="ot"> backward ::</span> <span class="dt">AnIso'</span> a b <span class="ot">-></span> <span class="dt">Isomorphism</span> a b</span>
<span id="cb31-18"><a href="#cb31-18" aria-hidden="true" tabindex="-1"></a> backward iso <span class="ot">=</span></span>
<span id="cb31-19"><a href="#cb31-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Isomorphism</span></span>
<span id="cb31-20"><a href="#cb31-20" aria-hidden="true" tabindex="-1"></a> { forward <span class="ot">=</span> view (cloneIso iso)</span>
<span id="cb31-21"><a href="#cb31-21" aria-hidden="true" tabindex="-1"></a> , backward <span class="ot">=</span> review (cloneIso iso)</span>
<span id="cb31-22"><a href="#cb31-22" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<h4 id="generalized-isomorphisms">Generalized isomorphisms</h4>
<p>I mentioned earlier that the isomorphism definition we began with was
not the fully general definition. In this section we’ll slightly
generalize the definition, while still sticking to something ergonomic
to express within Haskell:</p>
<blockquote>
<p>Two types, <code>A</code>, and <code>B</code>, are isomorphic if
there exist two morphisms, <code>forward</code> and
<code>backward</code> of the following types:</p>
<div class="sourceCode" id="cb32"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a><span class="ot">forward ::</span> cat <span class="dt">A</span> <span class="dt">B</span></span>
<span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb32-3"><a href="#cb32-3" aria-hidden="true" tabindex="-1"></a><span class="ot">backward ::</span> cat <span class="dt">B</span> <span class="dt">A</span></span></code></pre></div>
<p>… such that <code>cat</code> is an instance of the
<code>Category</code> type class and the following two equations (which
I will refer to as the “isomorphism laws”) are true:</p>
<div class="sourceCode" id="cb33"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb33-1"><a href="#cb33-1" aria-hidden="true" tabindex="-1"></a>forward <span class="op">.</span> backward <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb33-2"><a href="#cb33-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb33-3"><a href="#cb33-3" aria-hidden="true" tabindex="-1"></a>backward <span class="op">.</span> forward <span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
<p>… where <code>(.)</code> and <code>id</code> are the methods of the
<code>Category</code> type class and not necessarily the
<code>(.)</code> and <code>id</code> from the Prelude.</p>
</blockquote>
<p>This definition is based on the <a
href="https://hackage.haskell.org/package/base/docs/Control-Category.html#t:Category"><code>Category</code></a>
type class from the <a
href="https://hackage.haskell.org/package/base/docs/Control-Category.html"><code>Control.Category</code></a>
module, which is defined like this:</p>
<div class="sourceCode" id="cb34"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">Category</span> cat <span class="kw">where</span></span>
<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a> <span class="co">-- | the identity morphism</span></span>
<span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a><span class="ot"> id ::</span> cat a a</span>
<span id="cb34-4"><a href="#cb34-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-5"><a href="#cb34-5" aria-hidden="true" tabindex="-1"></a> <span class="co">-- | morphism composition</span></span>
<span id="cb34-6"><a href="#cb34-6" aria-hidden="true" tabindex="-1"></a><span class="ot"> (.) ::</span> cat b c <span class="ot">-></span> cat a b <span class="ot">-></span> cat a c</span></code></pre></div>
<p>… and all instance of the <code>Category</code> class must satisfy
the following three “category laws”:</p>
<div class="sourceCode" id="cb35"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb35-1"><a href="#cb35-1" aria-hidden="true" tabindex="-1"></a>(f <span class="op">.</span> g) <span class="op">.</span> h <span class="ot">=</span> f <span class="op">.</span> (g <span class="op">.</span> h)</span>
<span id="cb35-2"><a href="#cb35-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb35-3"><a href="#cb35-3" aria-hidden="true" tabindex="-1"></a>f <span class="op">.</span> <span class="fu">id</span> <span class="ot">=</span> f</span>
<span id="cb35-4"><a href="#cb35-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb35-5"><a href="#cb35-5" aria-hidden="true" tabindex="-1"></a><span class="fu">id</span> <span class="op">.</span> f <span class="ot">=</span> f</span></code></pre></div>
<p>In other words, you can think of the <code>Category</code> class as
generalizing our notion of functions to become “morphisms” so that we
replace values of type <code>a -> b</code> (functions) with values of
type <code>cat a b</code> (“morphisms”). When we generalize our notion
of functions to morphisms then we can similarly generalize our notion of
isomorphisms.</p>
<p>Of course, Haskell functions are one instance of this
<code>Category</code> class:</p>
<div class="sourceCode" id="cb36"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb36-1"><a href="#cb36-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Category</span> (<span class="ot">-></span>) <span class="kw">where</span></span>
<span id="cb36-2"><a href="#cb36-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">id</span> <span class="ot">=</span> Prelude.id</span>
<span id="cb36-3"><a href="#cb36-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb36-4"><a href="#cb36-4" aria-hidden="true" tabindex="-1"></a> (<span class="op">.</span>) <span class="ot">=</span> (<span class="op">Prelude..</span>)</span></code></pre></div>
<p>… so if we take our more general definition of isomorphisms and
replace <code>cat</code> with <code>(->)</code> then we get back the
less general definition of isomorphisms that we started with.</p>
<p>However, things other than functions can be instances of this
<code>Category</code> class, too. For example, “monadic” functions of
type <code>Monad m => a -> m b</code> can implement
<code>Category</code>, too, if we wrap them in a newtype:</p>
<div class="sourceCode" id="cb37"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb37-1"><a href="#cb37-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Category</span> (<span class="dt">Category</span>(..))</span>
<span id="cb37-2"><a href="#cb37-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad</span> ((<=<))</span>
<span id="cb37-3"><a href="#cb37-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb37-4"><a href="#cb37-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- Note: This type and instance already exists in the `Control.Arrow` module</span></span>
<span id="cb37-5"><a href="#cb37-5" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Kleisli</span> m a b <span class="ot">=</span> <span class="dt">Kleisli</span>{<span class="ot"> runKleisli ::</span> a <span class="ot">-></span> m b }</span>
<span id="cb37-6"><a href="#cb37-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb37-7"><a href="#cb37-7" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monad</span> m <span class="ot">=></span> <span class="dt">Category</span> (<span class="dt">Kleisli</span> m) <span class="kw">where</span></span>
<span id="cb37-8"><a href="#cb37-8" aria-hidden="true" tabindex="-1"></a> <span class="fu">id</span> <span class="ot">=</span> <span class="dt">Kleisli</span> <span class="fu">return</span></span>
<span id="cb37-9"><a href="#cb37-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb37-10"><a href="#cb37-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">Kleisli</span> f <span class="op">.</span> <span class="dt">Kleisli</span> g <span class="ot">=</span> <span class="dt">Kleisli</span> (f <span class="op"><=<</span> g)</span></code></pre></div>
<p>… and that satisfies the category laws because:</p>
<div class="sourceCode" id="cb38"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb38-1"><a href="#cb38-1" aria-hidden="true" tabindex="-1"></a>(f <span class="op"><=<</span> g) <span class="op"><=<</span> h <span class="ot">=</span> f <span class="op"><=<</span> (g <span class="op"><=<</span> h)</span>
<span id="cb38-2"><a href="#cb38-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb38-3"><a href="#cb38-3" aria-hidden="true" tabindex="-1"></a>f <span class="op"><=<</span> <span class="fu">return</span> <span class="ot">=</span> f</span>
<span id="cb38-4"><a href="#cb38-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb38-5"><a href="#cb38-5" aria-hidden="true" tabindex="-1"></a><span class="fu">return</span> <span class="op"><=<</span> f <span class="ot">=</span> f</span></code></pre></div>
<blockquote>
<p>Fun fact: The above category laws for the <code>Kleisli</code> type
constructor are isomorphic to the monad laws (in a different sense of
the world "isomorphic" that I have not covered).</p>
</blockquote>
<p>Once we begin to use <code>Category</code> instances other than
functions we can begin to explore more interesting types of “morphisms”
and “isomorphisms”. However, in order to do so we need to generalize our
<code>Isomorphism</code> type like this:</p>
<div class="sourceCode" id="cb39"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb39-1"><a href="#cb39-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Isomorphism</span> cat a b <span class="ot">=</span> <span class="dt">Isomorphism</span></span>
<span id="cb39-2"><a href="#cb39-2" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> forward ::</span> cat a b</span>
<span id="cb39-3"><a href="#cb39-3" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> backward ::</span> cat b a</span>
<span id="cb39-4"><a href="#cb39-4" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>… so that we can store morphisms that are not necessarily
functions.</p>
<p>With that generalized <code>Isomorphism</code> type in hand we can
now create a sample <code>Isomorphism</code> in a <code>Kleisli</code>
<code>Category</code>:</p>
<div class="sourceCode" id="cb40"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb40-1"><a href="#cb40-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Monoid</span> (<span class="dt">Sum</span>(..))</span>
<span id="cb40-2"><a href="#cb40-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Writer</span> (<span class="dt">Writer</span>)</span>
<span id="cb40-3"><a href="#cb40-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb40-4"><a href="#cb40-4" aria-hidden="true" tabindex="-1"></a><span class="ot">writerIsomorphism ::</span> <span class="dt">Isomorphism</span> (<span class="dt">Kleisli</span> (<span class="dt">Writer</span> (<span class="dt">Sum</span> <span class="dt">Integer</span>))) () ()</span>
<span id="cb40-5"><a href="#cb40-5" aria-hidden="true" tabindex="-1"></a>writerIsomorphism <span class="ot">=</span> <span class="dt">Isomorphism</span>{ forward, backward }</span>
<span id="cb40-6"><a href="#cb40-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb40-7"><a href="#cb40-7" aria-hidden="true" tabindex="-1"></a><span class="ot"> forward ::</span> <span class="dt">Kleisli</span> (<span class="dt">Writer</span> (<span class="dt">Sum</span> <span class="dt">Integer</span>)) () ()</span>
<span id="cb40-8"><a href="#cb40-8" aria-hidden="true" tabindex="-1"></a> forward <span class="ot">=</span> <span class="dt">Kleisli</span> (\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> <span class="dv">1</span>))</span>
<span id="cb40-9"><a href="#cb40-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb40-10"><a href="#cb40-10" aria-hidden="true" tabindex="-1"></a><span class="ot"> backward ::</span> <span class="dt">Kleisli</span> (<span class="dt">Writer</span> (<span class="dt">Sum</span> <span class="dt">Integer</span>)) () ()</span>
<span id="cb40-11"><a href="#cb40-11" aria-hidden="true" tabindex="-1"></a> backward <span class="ot">=</span> <span class="dt">Kleisli</span> (\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> (<span class="op">-</span><span class="dv">1</span>)))</span></code></pre></div>
<p>Like before, we still require that:</p>
<div class="sourceCode" id="cb41"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb41-1"><a href="#cb41-1" aria-hidden="true" tabindex="-1"></a>forward <span class="op">.</span> backward <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb41-2"><a href="#cb41-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb41-3"><a href="#cb41-3" aria-hidden="true" tabindex="-1"></a>backward <span class="op">.</span> forward <span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
<p>… but in this case the <code>(.)</code> and <code>id</code> in these
two isomorphism laws will be the ones for our <code>Kleisli</code> type
instead of the ones for functions.</p>
<details>
<summary>
Proof of isomorphism laws (click to expand)
</summary>
<p>I’ll skip over several steps for this proof to highlight the relevant
parts:</p>
<div class="sourceCode" id="cb42"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb42-1"><a href="#cb42-1" aria-hidden="true" tabindex="-1"></a>forward <span class="op">.</span> backward</span>
<span id="cb42-2"><a href="#cb42-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb42-3"><a href="#cb42-3" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Kleisli</span> (\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> <span class="dv">1</span>)) <span class="op">.</span> <span class="dt">Kleisli</span> (\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> (<span class="op">-</span><span class="dv">1</span>)))</span>
<span id="cb42-4"><a href="#cb42-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb42-5"><a href="#cb42-5" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Kleisli</span> ((\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> <span class="dv">1</span>)) <span class="op"><=<</span> (\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> (<span class="op">-</span><span class="dv">1</span>))))</span>
<span id="cb42-6"><a href="#cb42-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb42-7"><a href="#cb42-7" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Kleisli</span> (\_ <span class="ot">-></span> tell (<span class="dt">Sum</span> <span class="dv">0</span>))</span>
<span id="cb42-8"><a href="#cb42-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb42-9"><a href="#cb42-9" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Kleisli</span> <span class="fu">return</span></span>
<span id="cb42-10"><a href="#cb42-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb42-11"><a href="#cb42-11" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
The proof of <code>backward . forward = id</code> is essentially the
same thing, except flipped.
</details>
<p>Note our <code>Isomorphism</code> effectively says that the type
<code>()</code> is isomorphic to the type <code>()</code> within this
<code>Kleisli (Writer (Sum Integer))</code> <code>Category</code>, which
is not a very interesting conclusion. Rather, for this
<code>Isomorphism</code> the (slightly more) interesting bit is in the
“morphisms” (the <code>forward</code> and <code>backward</code>
definitions), which are inverses of one another.</p>
<p>Here is one last example of a non-trivial <code>Category</code>
instance with an example isomorphism:</p>
<div class="sourceCode" id="cb43"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb43-1"><a href="#cb43-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span> <span class="kw">hiding</span> ((.), id)</span>
<span id="cb43-2"><a href="#cb43-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-3"><a href="#cb43-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Note: This is not how the lens package works, but it's still a useful example</span></span>
<span id="cb43-4"><a href="#cb43-4" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Lens</span> a b <span class="ot">=</span> <span class="dt">Lens</span>{<span class="ot"> view ::</span> a <span class="ot">-></span> b,<span class="ot"> over ::</span> (b <span class="ot">-></span> b) <span class="ot">-></span> (a <span class="ot">-></span> a) }</span>
<span id="cb43-5"><a href="#cb43-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-6"><a href="#cb43-6" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Category</span> <span class="dt">Lens</span> <span class="kw">where</span></span>
<span id="cb43-7"><a href="#cb43-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">id</span> <span class="ot">=</span> <span class="dt">Lens</span>{ view <span class="ot">=</span> <span class="fu">id</span>, over <span class="ot">=</span> <span class="fu">id</span> }</span>
<span id="cb43-8"><a href="#cb43-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-9"><a href="#cb43-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">Lens</span>{ view <span class="ot">=</span> viewL, over <span class="ot">=</span> overL } <span class="op">.</span> <span class="dt">Lens</span>{ view <span class="ot">=</span> viewR, over <span class="ot">=</span> overR } <span class="ot">=</span></span>
<span id="cb43-10"><a href="#cb43-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">Lens</span>{ view <span class="ot">=</span> viewL <span class="op">.</span> viewR, over <span class="ot">=</span> overR <span class="op">.</span> overL }</span>
<span id="cb43-11"><a href="#cb43-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-12"><a href="#cb43-12" aria-hidden="true" tabindex="-1"></a><span class="ot">lensIsomorphism ::</span> <span class="dt">Isomorphism</span> <span class="dt">Lens</span> <span class="dt">Bool</span> <span class="dt">Bool</span></span>
<span id="cb43-13"><a href="#cb43-13" aria-hidden="true" tabindex="-1"></a>lensIsomorphism <span class="ot">=</span> <span class="dt">Isomorphism</span>{ forward, backward }</span>
<span id="cb43-14"><a href="#cb43-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb43-15"><a href="#cb43-15" aria-hidden="true" tabindex="-1"></a><span class="ot"> forward ::</span> <span class="dt">Lens</span> <span class="dt">Bool</span> <span class="dt">Bool</span></span>
<span id="cb43-16"><a href="#cb43-16" aria-hidden="true" tabindex="-1"></a> forward <span class="ot">=</span> <span class="dt">Lens</span>{ view <span class="ot">=</span> <span class="fu">not</span>, over <span class="ot">=</span> \f <span class="ot">-></span> <span class="fu">not</span> <span class="op">.</span> f <span class="op">.</span> <span class="fu">not</span> }</span>
<span id="cb43-17"><a href="#cb43-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-18"><a href="#cb43-18" aria-hidden="true" tabindex="-1"></a> <span class="co">-- There is no rule that the two morphisms can't be the same</span></span>
<span id="cb43-19"><a href="#cb43-19" aria-hidden="true" tabindex="-1"></a><span class="ot"> backward ::</span> <span class="dt">Lens</span> <span class="dt">Bool</span> <span class="dt">Bool</span></span>
<span id="cb43-20"><a href="#cb43-20" aria-hidden="true" tabindex="-1"></a> backward <span class="ot">=</span> forward</span></code></pre></div>
<p>Again, it’s not very interesting to say that <code>Bool</code> is
isomorphic to <code>Bool</code>, but it is more to note that the
<code>forward</code> lens is essentially its own inverse.</p>
<p>There’s one last category I want to quickly mention, which is …
<code>Isomorphism</code>!</p>
<p>Yes, the <code>Isomorphism</code> type we introduced is itself an
instance of the <code>Category</code> class:</p>
<div class="sourceCode" id="cb44"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb44-1"><a href="#cb44-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Category</span> cat <span class="ot">=></span> <span class="dt">Category</span> (<span class="dt">Isomorphism</span> cat) <span class="kw">where</span></span>
<span id="cb44-2"><a href="#cb44-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">Isomorphism</span> forwardL backwardL <span class="op">.</span> <span class="dt">Isomorphism</span> forwardR backwardR <span class="ot">=</span></span>
<span id="cb44-3"><a href="#cb44-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">Isomorphism</span> (forwardL <span class="op">.</span> forwardR) (backwardR <span class="op">.</span> backwardL)</span>
<span id="cb44-4"><a href="#cb44-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb44-5"><a href="#cb44-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">id</span> <span class="ot">=</span> <span class="dt">Isomorphism</span> <span class="fu">id</span> <span class="fu">id</span></span></code></pre></div>
<p>You might even say that an “isomorphism” is a “morphism” in the above
<code>Category</code>. An “iso”-“morphism”, if you will (where “iso”
means “same”).</p>
<p>Furthermore, we can create an example <code>Isomorphism</code> in
this <code>Category</code> of <code>Isomorphism</code>s:</p>
<div class="sourceCode" id="cb45"><pre
class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb45-1"><a href="#cb45-1" aria-hidden="true" tabindex="-1"></a><span class="ot">nestedIsomorphism ::</span> <span class="dt">Isomorphism</span> (<span class="dt">Isomorphism</span> (<span class="ot">-></span>)) <span class="dt">Integer</span> <span class="dt">Integer</span></span>
<span id="cb45-2"><a href="#cb45-2" aria-hidden="true" tabindex="-1"></a>nestedIsomorphism <span class="ot">=</span></span>
<span id="cb45-3"><a href="#cb45-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">Isomorphism</span></span>
<span id="cb45-4"><a href="#cb45-4" aria-hidden="true" tabindex="-1"></a> { forward <span class="ot">=</span> <span class="dt">Isomorphism</span>{ forward <span class="ot">=</span> (<span class="op">+</span> <span class="dv">1</span>), backward <span class="ot">=</span> <span class="fu">subtract</span> <span class="dv">1</span> }</span>
<span id="cb45-5"><a href="#cb45-5" aria-hidden="true" tabindex="-1"></a> , backward <span class="ot">=</span> <span class="dt">Isomorphism</span>{ forward <span class="ot">=</span> <span class="fu">subtract</span> <span class="dv">1</span>, backward <span class="ot">=</span> (<span class="op">+</span> <span class="dv">1</span>) }</span>
<span id="cb45-6"><a href="#cb45-6" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>Okay, perhaps that’s going a bit too far, but I just wanted to end
this post with a cute example of how you can keep chaining these ideas
together in new ways.</p>
<h4 id="conclusion">Conclusion</h4>
<p>In my experience, the more you train your ability to reason formally
about isomorphisms the more you broaden your ability to recognize
disparate things as equivalent and draw interesting connections between
them.</p>
<p>For example, fluency with many common isomorphisms is a useful skill
for API design because often there might be a way to take an API which
is not very ergonomic and refactor it into an equivalent (isomorphic)
API which is more ergonomic to use.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com2tag:blogger.com,1999:blog-1777990983847811806.post-81363292599911335702022-09-07T06:53:00.006-07:002022-09-07T08:56:33.607-07:00nix-serve-ng: A faster, more reliable, drop-in replacement for nix-serve<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="nix-serve-ng: A faster, more reliable, drop-in replacement for nix-serve">
<meta name="twitter:description" content="An announcement post for a Haskell rewrite of nix-serve named nix-serve-ng">
<title>nix-serve-ng</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>Our team at <a href="https://www.arista.com/en/">Arista Networks</a>
is happy to announce nix-serve-ng, a backwards-compatible Haskell
rewrite of <a href="https://github.com/edolstra/nix-serve">nix-serve</a>
(a service for hosting a <code>/nix/store</code> as a binary cache). It provides better reliability and performance
than nix-serve (ranging from ≈ 1.5× to 32× faster). We wrote
nix-serve-ng to fix scaling bottlenecks in our cache and we expect other
large-scale deployments might be interested in this project, too.</p>
<p>This post will focus more on the background behind the development
process and comparisons to other Nix cache implementations. If you don’t
care about any of that then you can get started by following the
instructions in <a
href="https://github.com/aristanetworks/nix-serve-ng#nix-serve-ng">the
repository’s <code>README</code></a>.</p>
<h4 id="background">Background</h4>
<p>Before we began this project there were at least two other open
source rewrites of <code>nix-serve-ng</code> that we could have adopted
instead of <code>nix-serve</code>:</p>
<ul>
<li><p><a
href="https://github.com/thoughtpolice/eris"><code>eris</code></a> - A
Perl rewrite of <code>nix-serve</code></p>
<p>Note: the original <code>nix-serve</code> is implemented in Perl, and
eris is also implemented in Perl using a different framework.</p></li>
<li><p><a
href="https://github.com/helsinki-systems/harmonia"><code>harmonia</code></a>
- A Rust rewrite of <code>nix-serve</code></p></li>
</ul>
<p>The main reason we did not go with these two alternatives is because
they are not drop-in replacements for the original
<code>nix-serve</code>. We could have fixed that, but given <a
href="https://github.com/edolstra/nix-serve/blob/master/nix-serve.psgi">how
simple <code>nix-serve</code> is</a> I figured that it would be simpler
to just create our own. <code>nix-serve-ng</code> only took a couple of
days for the initial version and maybe a week of follow-up fixes and
performance tuning.</p>
<p>We did not evaluate the performance or reliability of
<code>eris</code> or <code>harmonia</code> before embarking on our own
<code>nix-serve</code> replacement. However, after
<code>nix-serve-ng</code> was done we learned that it was significantly
faster than the alternatives (see the <a
href="#performance">Performance</a> section below). Some of those
performance differences are probably fixable, especially for
<code>harmonia</code>. That said, we are very happy with the quality of
our solution.</p>
<h4 id="backwards-compatibility">Backwards compatibility</h4>
<p>One important design goal for this project is to be significantly
backwards compatible with <code>nix-serve</code>. We went to great
lengths to preserve compatibility, including:</p>
<ul>
<li><p>Naming the built executable <code>nix-serve</code></p>
<p>Yes, even though the project name is <code>nix-serve-ng</code>, the
executable built by the project is named
<code>nix-serve</code>.</p></li>
<li><p>Preserving most of the original command-line options, including
legacy options</p>
<p>… even though some are unused.</p></li>
</ul>
<p>In most cases you can literally replace <code>pkgs.nix-serve</code>
with <code>pkgs.nix-serve-ng</code> and it will “just work”. You can
even continue to use the existing <code>services.nix-serve</code> NixOS
options.</p>
<p>The biggest compatibility regression is that
<code>nix-serve-ng</code> cannot be built on MacOS. It is extremely
close to supporting MacOS save for this one bug in Haskell’s
<code>hsc2hs</code> tool: <a
href="https://github.com/haskell/hsc2hs/issues/26"><code>haskell/hsc2hs</code>
- #26</a>. We left in all of the MacOS shims so that if that bug is ever
fixed then we can get MacOS support easily.</p>
<p>For more details on the exact differences compared to
<code>nix-serve</code>, see the <a
href="https://github.com/aristanetworks/nix-serve-ng#results">Result /
Backwards-compatibility</a> section of the <code>README</code>.</p>
<h4 id="performance">Performance</h4>
<p><code>nix-serve-ng</code> is faster than all of the alternatives
according to both our formal benchmarks and also informal testing. The
<a
href="https://github.com/aristanetworks/nix-serve-ng#benchmarks">“Benchmarks”
section of our <code>README</code></a> has the complete breakdown but
the relevant part is this table:</p>
<p>Speedups (compared to <code>nix-serve</code>):</p>
<table>
<colgroup>
<col style="width: 28%" />
<col style="width: 18%" />
<col style="width: 18%" />
<col style="width: 18%" />
<col style="width: 18%" />
</colgroup>
<thead>
<tr class="header">
<th>Benchmark</th>
<th><code>nix-serve</code></th>
<th><code>eris</code></th>
<th><code>harmonia</code></th>
<th><code>nix-serve-ng</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Fetch present NAR info ×10</td>
<td>1.0</td>
<td>0.05</td>
<td>1.33</td>
<td>1.58</td>
</tr>
<tr class="even">
<td>Fetch absent NAR info ×1</td>
<td>1.0</td>
<td>0.06</td>
<td>1.53</td>
<td>1.84</td>
</tr>
<tr class="odd">
<td>Fetch empty NAR ×10</td>
<td>1.0</td>
<td>0.67</td>
<td>0.59</td>
<td>31.80</td>
</tr>
<tr class="even">
<td>Fetch 10 MB NAR ×10</td>
<td>1.0</td>
<td>0.64</td>
<td>0.60</td>
<td>3.35</td>
</tr>
</tbody>
</table>
<p>… which I can summarize like this:</p>
<ul>
<li><code>nix-serve-ng</code> is faster than all of the alternatives
across all use cases</li>
<li><code>eris</code> is slower than the original <code>nix-serve</code>
across all use cases</li>
<li><code>harmonia</code> is faster than the original
<code>nix-serve</code> for NAR info lookups, but slower for fetching
NARs</li>
</ul>
<p>These performance results were surprising for a few reasons:</p>
<ul>
<li><p>I was not expecting <code>eris</code> to be slower than the
original <code>nix-serve</code> implementation</p>
<p>… especially not NAR info lookups to be ≈ 20× slower. This is
significant because NAR info lookups typically dominate a Nix cache’s
performance. In my (informal) experience, the majority of a Nix cache’s
time is spent addressing failed cache lookups.</p></li>
<li><p>I was not expecting <code>harmonia</code> (the Rust rewrite) to
be slower than the original <code>nix-serve</code> for fetching NARs</p>
<p>This seems like something that should be fixable.
<code>harmonia</code> will probably eventually match our performance
because Rust has a high performance ceiling.</p></li>
<li><p>I was not expecting a ≈ 30x speedup for <code>nix-serve-ng</code>
fetching small NARs</p>
<p>I had to triple-check that neither <code>nix-serve-ng</code> nor the
benchmark were broken when I saw this speedup.</p></li>
</ul>
<p>So I investigated these performance differences to help inform other
implementations what to be mindful of.</p>
<h4 id="performance-insights">Performance insights</h4>
<p>We didn’t get these kinds of speed-ups by being completely oblivious
to performance. Here are the things that we paid special attention to to
keep things efficient, in order of lowest-hanging to highest-hanging
fruit:</p>
<ul>
<li><p>Don’t read the secret key file on every NAR fetch</p>
<p>This is a silly thing that <a
href="https://github.com/edolstra/nix-serve/blob/8501f2e44e7963eb1fb20146a055cfe7242a2ac4/nix-serve.psgi#L38-L40">the
original <code>nix-serve</code> does</a> that is the easiest thing to
fix.</p>
<p><code>eris</code> and <code>harmonia</code> also fix this, so this
optimization is not unique to our rewrite.</p></li>
<li><p>We bind directly to the Nix C++ API for fetching NARs</p>
<p><code>nix-serve</code>, <code>eris</code>, and <code>harmonia</code>
all shell out to a subprocess to fetch NARs, by invoking either
<code>nix dump-path</code> or <code>nix-store --dump</code> to do the
heavy lifting. In contrast, <code>nix-serve-ng</code> binds to the Nix
C++ API for this purpose.</p>
<p>This would definitely explain some of the performance difference when
fetching NARs. Creating a subprocess has a fixed overhead regardless of
the size of the NAR, which explains why we see the largest performance
difference when fetching tiny NARs since the overhead of creating a
subprocess would dominate the response time.</p>
<p>This may also affect throughput for serving large NAR files, too, by
adding unnecessary memory copies/buffering as part of streaming the
subprocess output.</p></li>
<li><p>We minimize memory copies when fetching NARs</p>
<p>We go to great lengths to minimize the number of intermediate buffers
and copies when streaming the contents of a NAR to a client. To do this,
we exploit the fact that Haskell’s foreign function interface works in
both directions: Haskell code can call C++ code but also C++ code can
call Haskell code. This means that we can <a
href="https://github.com/aristanetworks/nix-serve-ng/blob/ba2028f9d7fd2f93a04e1ca9677811f461fe6676/cbits/nix.cpp#L173-L185">create
a Nix C++ streaming sink from a Haskell callback function</a> and this
eliminates the need for intermediate buffers.</p>
<p>This likely also improves the throughput for serving NAR files. Only
<code>nix-serve-ng</code> performs this optimization (since
<code>nix-serve-ng</code> is the only one that uses the C++ API for
streaming NAR contents).</p></li>
<li><p>Hand-write the API routing logic</p>
<p>We hand-write all of the API routing logic to prioritize and optimize
the hot path (fetching NAR info).</p>
<p>For example, a really simple thing that the original
<code>nix-serve</code> does inefficiently is to check if the path
matches <code>/nix-cache-info</code> first, even though that is an
extremely infrequently used path. In our API routing logic we move that
check straight to the very end.</p>
<p>These optimizations likely improve the performance of NAR info
requests. As far as I can tell, only <code>nix-serve-ng</code> performs
these optimizations.</p></li>
</ul>
<p>I have not benchmarked the performance impact of each of these
changes in isolation, though. These observations are purely based on my
intuition.</p>
<h4 id="features">Features</h4>
<p><code>nix-serve-ng</code> is not all upsides. In particular,
<code>nix-serve-ng</code> is missing features that some of the other
rewrites provide, such as:</p>
<ul>
<li>Greater configurability</li>
<li>Improved authentication support</li>
<li>Monitoring/diagnostics/status APIs</li>
</ul>
<p>Our focus was entirely on scalability, so the primary reason to use
<code>nix-serve-ng</code> is if you prioritize performance and
uptime.</p>
<h4 id="conclusion">Conclusion</h4>
<p>We’ve been using <code>nix-serve-ng</code> long enough internally
that we feel confident endorsing its use outside our company. We run a
particularly large Nix deployment internally (which is why we needed
this in the first place), so we have stress tested
<code>nix-serve-ng</code> considerably under heavy and realistic usage
patterns.</p>
<p>You can get started by following these <a
href="https://github.com/aristanetworks/nix-serve-ng/blob/main/README.md#quick-start">these
instructions</a> and let us know if you run into any issues or
difficulties.</p>
<p>Also, I want to thank <a href="https://www.arista.com/en/">Arista
Networks</a> for graciously sponsoring our team to work on and open
source this project</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-9066207651109299692022-08-29T06:16:00.008-07:002023-04-03T11:48:06.699-07:00Stop calling everything "Nix"<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="Stop calling everything "Nix"">
<meta name="twitter:description" content="Explanation of the Nix ecosystem's abstraction layers">
<title>Stop calling everything "Nix"</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>One of my pet peeves is when people abuse the term “Nix” without
qualification when trying to explain the various components of the Nix
ecosystem.</p>
<p>As a concrete example, a person might say:</p>
<p>“I hate Nix’s syntax”</p>
<p>… and when you dig into this criticism you realize that they’re
actually complaining about the Nixpkgs API, which is not the same thing
as the syntax of the Nix expression language.</p>
<p>So one of the goals of this post is to introduce some unambiguous
terminology that people can use to refer to the various abstraction
layers of the Nix ecosystem in order to avoid confusion. I’ll introduce
each abstraction layer from the lowest level abstractions to the highest
level abstractions.</p>
<p>Another reason I explain “Nix” in terms of these abstraction layers
is because this helps people consult the correct manual. The Nix
ecosystem provides three manuals that you will commonly need to refer to
in order to become more proficient:</p>
<ul>
<li>The <a href="https://nixos.org/manual/nix/stable/">Nix
manual</a></li>
<li>The <a href="https://nixos.org/manual/nixpkgs/stable/">Nixpkgs
manual</a></li>
<li>The <a href="https://nixos.org/manual/nixos/stable/">NixOS
manual</a></li>
</ul>
<p>… and I hope by the end of this post it will be clearer which manual
interests you for any given question.</p>
<p>Edit: <a
href="https://twitter.com/domenkozar/status/1564241948319404032">Domen
Kožar pointed out</a> that there is an ongoing effort to standardize
terminology here:</p>
<ul>
<li><a
href="https://github.com/NixOS/nix.dev/issues/275"><code>NixOS/nix.dev</code>
#275 - establish nomenclature</a></li>
</ul>
<p>I’ll update the post to match the agreed-upon terminology when that
is complete.</p>
<h4 id="layer-0-the-nix-store">Layer #0: The Nix store</h4>
<p>I use the term “Nix store” to mean essentially everything you can
manage with the <code>nix-store</code> command-line tool.</p>
<p>That is the simplest definition, but to expand upon that, I mean the
following files:</p>
<ul>
<li>Derivations: <code>/nix/store/*.drv</code></li>
<li>Build products: <code>/nix/store/*</code> without a
<code>.drv</code> extension</li>
<li>Log files: <code>/nix/var/log/nix/drvs/**</code></li>
<li>Garbage collection roots: <code>/nix/var/nix/gcroots/**</code></li>
</ul>
<p>… and the following operations:</p>
<ul>
<li><p>Realizing a derivation</p>
<p>i.e. converting a <code>.drv</code> file to the corresponding build
products using <code>nix-store --realise</code></p></li>
<li><p>Adding static files to the <code>/nix/store</code></p>
<p>i.e. <code>nix-store --add</code></p></li>
<li><p>Creating GC roots for build products</p>
<p>i.e. the <code>--add-root</code> option to
<code>nix-store</code></p></li>
<li><p>Garbage collecting derivations not protected by a GC root</p>
<p>i.e. <code>nix-store --gc</code></p></li>
</ul>
<p>There are other things the Nix store supports (like profile
management), but these are the most important operations.</p>
<p><strong>CAREFULLY NOTE:</strong> the “Nix store” is independent of
the “Nix language” (which we’ll define below). In other words, you could
replace the front-end Nix programming language with another language
(e.g. Guile scheme, as Guix does). This is because the Nix derivation
format (the <code>.drv</code> files) and the <code>nix-store</code>
command-line interface are both agnostic of the Nix expression language.
I have a talk which delves a bit more into this subject:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=GMQPzv3Sx58">Nix: Under the
hood</a></li>
</ul>
<h4 id="layer-1-the-nix-language">Layer #1: The Nix language</h4>
<p>I use the term “Nix language” to encompass three things:</p>
<ul>
<li>The programming language: source code we typically store in
<code>.nix</code> files</li>
<li>Instantiation: the interpretation of Nix code to generate
<code>.drv</code> files</li>
<li><a href="https://nixos.wiki/wiki/Flakes">Flakes</a>: pure evaluation
and instantiation caching</li>
</ul>
<p>To connect this with the previous section, the typical pipeline for
converting Nix source code to a build product is:</p>
<pre><code>Nix source code (*.nix) │ Nix language
↓ Instantiation ├─────────────
Nix derivation (/nix/store/*.drv) │
↓ Realization │ Nix store
Nix build product (/nix/store/*) │</code></pre>
<p>In isolation, the Nix language is “just” a purely functional
programming language with simple language constructs. For example, here
is a sample Nix REPL session:</p>
<pre class="nix"><code>nix-repl> 2 + 2
4
nix-repl> x = "world"
nix-repl> "Hello, " + x
"Hello, world"
nix-repl> r = { a = 1; b = true; }
nix-repl> if r.b then r.a else 0
1</code></pre>
<p>However, as we go up the abstraction ladder the idiomatic Nix code
we’ll encounter will begin to stray from that simple functional
core.</p>
<p><strong>NOTE:</strong> Some people will disagree with my choice to
include flakes at this abstraction layer since flakes are sometimes
marketed as a dependency manager (similar to <code>niv</code>). I don’t
view them in this way and I treat flakes as primarily as mechanism for
purifying evaluation and caching instantiation, as outlined in this
post:</p>
<ul>
<li><a href="https://www.tweag.io/blog/2020-06-25-eval-cache/">Nix
Flakes, Part 2: Evaluation Caching</a></li>
</ul>
<p>… and if you view flakes in that capacity then they are a feature of
the Nix language since evaluation/instantiation are the primary purpose
of the programming language.</p>
<h4 id="layer-2-the-nix-build-tool">Layer #2: The Nix build tool</h4>
<p>This layer encompasses the command-line interface to both the “Nix
store” and the “Nix language”.</p>
<p>This includes (but is not limited to):</p>
<ul>
<li><code>nix-store</code> (the command, not the underlying store)</li>
<li><code>nix-instantiate</code></li>
<li><code>nix-build</code></li>
<li><code>nix-shell</code></li>
<li><code>nix</code> subcommands, including:
<ul>
<li><code>nix build</code></li>
<li><code>nix run</code></li>
<li><code>nix develop</code></li>
<li><code>nix log</code></li>
<li><code>nix flake</code></li>
</ul></li>
</ul>
<p>I make this distinction because the command-line interface enables
some additional niceties that are not inherent to the underlying layers.
For example, the <code>nix build</code> command has some flake
integration so that you can say
<code>nix build someFlake#somePackage</code> and this command-line API
nicety is not necessarily inherent to flakes (in my view).</p>
<p>Also, many of these commands operate at both Layer 0 and Layer 1,
which can blur the distinction between the two. For example the
<code>nix-build</code> command can accept a layer 1 Nix program (i.e. a
<code>.nix</code> file) or a layer 0 derivation (i.e. a
<code>.drv</code> file).</p>
<p>Another thing that blurs the distinction is that the Nix manual
covers all three of the layers introduced so far, ranging from the Nix
store to the command-line interface. However, if you want to better
understand these three layers then that is correct place to begin:</p>
<ul>
<li><a href="https://nixos.org/manual/nix/stable/introduction.html">Nix
manual</a></li>
</ul>
<h4 id="layer-3-nixpkgs">Layer #3: Nixpkgs</h4>
<p>Nixpkgs is a software distribution (a.k.a. “distro”) for Nix.
Specifically, all of the packaging logic for Nixpkgs is hosted on GitHub
here:</p>
<ul>
<li><a href="https://github.com/NixOS/nixpkgs">GitHub -
<code>NixOS/nixpkgs</code></a></li>
</ul>
<p>This repository contains a large number of Nix expressions for
building packages across several platforms. If the “Nix language” is a
programming language then “Nixpkgs” is a gigantic “library” authored
within that language. There are other Nix “libraries” outside of Nixpkgs
but Nixpkgs is the one you will interact with the most.</p>
<p>The Nixpkgs repository establishes several widespread idioms and
conventions, including:</p>
<ul>
<li>The standard environment (a.k.a. <code>stdenv</code>) for authoring
a package
<ul>
<li>There are also language-specific standard-environments, too</li>
</ul></li>
<li>A domain-specific language for overriding individual packages or
sets of packages</li>
</ul>
<p>When people complain about “Nix’s syntax”, most of the time they’re
actually complaining about Nixpkgs and more specifically complaining
about the Nixpkgs system for overriding packages. However, I can see how
people might mistake the two.</p>
<p>The reason for the confusion is that the Nixpkgs support for
overrides is essentially an embedded domain-specific language, meaning
that you still express everything in the Nix language (layer 1), but the
ways in which you express things is fundamentally different than if you
were simply using low-level Nix language features.</p>
<p>As a contrived example, this “layer 1” Nix code:</p>
<pre class="nix"><code>let
x = 1;
y = x + 2;</code></pre>
<p>… would roughly correspond to the following “layer 3” Nixpkgs
overlay:</p>
<pre class="nix"><code>self: super: {
x = 1;
y = self.x + 2;
}</code></pre>
<p>The reason why Nixpkgs doesn’t do the simpler “layer 1” thing is
because Nixpkgs is designed to support “late binding” of expressions,
meaning that everything can be overridden, even dependencies deep within
the dependency tree. Moreover, this overriding is done in such a way
that everything “downstream” of the overrride (i.e. all reverse
dependencies) pick up the change correctly.</p>
<p>As a more realistic example, the following program:</p>
<pre class="nix"><code>let
pkgs = import <nixpkgs> { };
fast-tags =
pkgs.haskell.lib.justStaticExecutables pkgs.haskellPackages.fast-tags;
fast-tags-no-tests =
pkgs.haskell.lib.dontCheck fast-tags;
in
fast-tags-no-tests</code></pre>
<p>… is simpler, but is not an idiomatic use of Nixpkgs because it is
not using the overlay system and therefore does not support late
binding. The more idiomatic analog would be:</p>
<pre class="nix"><code>let
overlay = self: super: {
fast-tags =
self.haskell.lib.justStaticExecutables self.haskellPackages.fast-tags;
fast-tags-no-tests =
self.haskell.lib.dontCheck self.fast-tags;
};
pkgs = import <nixpkgs> { overlays = [ overlay ]; };
in
pkgs.fast-tags-no-tests</code></pre>
<p>You can learn more about this abstraction layer by consulting the
Nixpkgs manual:</p>
<ul>
<li><a href="https://nixos.org/manual/nixpkgs/stable/">Nixpkgs
manual</a></li>
</ul>
<h4 id="layer-4-nixos">Layer #4: NixOS</h4>
<p>NixOS is an operating system that is (literally) built on
Nixpkgs. Specifically, there is a <code>./nixos/</code> subdirectory of
the Nixpkgs repository for all of the NixOS-related logic.</p>
<p>NixOS is based on the NixOS module system, which is yet another
embedded domain-specific language. In other words, you configure NixOS
with Nix code, but the idioms of that Nix code depart even more wildly
from straightforward “layer 1” Nix code.</p>
<p>NixOS modules were designed to look more like Terraform modules than
Nix code, but they are still technically Nix code. For example, this is
what the NixOS module for the <code>lorri</code> service looks like at
the time of this writing:</p>
<pre class="nix"><code>{ config, lib, pkgs, ... }:
let
cfg = config.services.lorri;
socketPath = "lorri/daemon.socket";
in {
options = {
services.lorri = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = lib.mdDoc ''
Enables the daemon for `lorri`, a nix-shell replacement for project
development. The socket-activated daemon starts on the first request
issued by the `lorri` command.
'';
};
package = lib.mkOption {
default = pkgs.lorri;
type = lib.types.package;
description = lib.mdDoc ''
The lorri package to use.
'';
defaultText = lib.literalExpression "pkgs.lorri";
};
};
};
config = lib.mkIf cfg.enable {
systemd.user.sockets.lorri = {
description = "Socket for Lorri Daemon";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = "%t/${socketPath}";
RuntimeDirectory = "lorri";
};
};
systemd.user.services.lorri = {
description = "Lorri Daemon";
requires = [ "lorri.socket" ];
after = [ "lorri.socket" ];
path = with pkgs; [ config.nix.package git gnutar gzip ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/lorri daemon";
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
Restart = "on-failure";
};
};
environment.systemPackages = [ cfg.package ];
};
}</code></pre>
<p>You might wonder how NixOS relates to the underlying layers. For
example, if Nix is a build system, then how do you “build” NixOS? I have
another post which elaborates on that subject here:</p>
<ul>
<li><a
href="https://www.haskellforall.com/2018/08/nixos-in-production.html">NixOS
in production</a></li>
</ul>
<p>Also, you can learn more about this abstraction layer by consulting
the NixOS manual:</p>
<ul>
<li><a href="https://nixos.org/manual/nixos/stable/">NixOS
manual</a></li>
</ul>
<h4 id="nix-ecosystem">Nix ecosystem</h4>
<p>I use the term “Nix ecosystem” to describe all of the preceding
layers and other stuff not mentioned so far (like <code>hydra</code>,
the continuous integration service).</p>
<p>This is not a layer of its own, but I mention this because I prefer
to use “Nix ecosystem” instead of “Nix” to avoid ambiguity, since the
latter can easily be mistaken for an individual abstraction layer
(especially the Nix language or the Nix build tool).</p>
<p>However, when I do hear people say “Nix”, then I generally understand
it to mean the “Nix ecosystem” unless they clarify otherwise.</p>
<h4 id="conclusion">Conclusion</h4>
<p>Hopefully this passive aggressive post helps people express
themselves a little more precisely when discussing the Nix
ecosystem.</p>
<p>If you enjoy this post, you will probably also like this other post
of mine:</p>
<ul>
<li><a
href="https://www.haskellforall.com/2022/03/the-hard-part-of-type-checking-nix.html">The
hard part of type-checking Nix</a></li>
</ul>
<p>… since that touches on the Nixpkgs and NixOS embedded
domain-specific languages and how they confound the user experience.</p>
<p>I’ll conclude this post with the following obligatory joke:</p>
<blockquote>
<p>I’d just like to interject for a moment. What you’re refering to as
Nix, is in fact, NixOS, or as I’ve recently taken to calling it, Nix
plus OS. Nix is not an operating system unto itself, but rather another
free component of a fully functioning ecosystem made useful by the Nix
store, Nix language, and Nix build tool comprising a full OS as defined
by POSIX.</p>
<p>Many Guix users run a modified version of the Nix ecosystem every
day, without realizing it. Through a peculiar turn of events, the
operating system based on Nix which is widely used today is often called
Nix, and many of its users are not aware that it is basically the Nix
ecosystem, developed by the NixOS foundation.</p>
<p>There really is a Nix, and these people are using it, but it is just
a part of the system they use. Nix is the expression language: the
program in the system that specifies the services and programs that you
want to build and run. The language is an essential part of the
operating system, but useless by itself; it can only function in the
context of a complete operating system. Nix is normally used in
combination with an operating system: the whole system is basically an
operating system with Nix added, or NixOS. All the so-called Nix
distributions are really distributions of NixOS!</p>
</blockquote>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com1tag:blogger.com,1999:blog-1777990983847811806.post-26138929086448134472022-08-28T08:23:00.007-07:002022-08-28T08:56:26.253-07:00Incrementally package a Haskell program using Nix<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@GabriellaG439">
<meta name="twitter:title" content="Incrementally package a Haskell program using Nix">
<meta name="twitter:description" content="A tour of progressively more robust ways to package a Haskell file using Nix">
<title>incremental-nix</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
h1 {
font-size: 1.8em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This post walks through how to take a standalone Haskell file and
progressively package the file using Nix. In other words, we will tour a
spectrum of packaging options ranging from simple to fancy.</p>
<p>The running example will be the following standalone single-file
Haskell program:</p>
<ul>
<li><a
href="https://github.com/Gabriella439/spire/blob/5a35feb5629df27d9c973d36d9bf940dd85445eb/Spire.hs">GitHub
- Gabriella439/spire - Spire.hs</a></li>
</ul>
<p>I won’t go into detail about what that program does, although you can
study the program if you are curious. Essentially, I’m planning to
deliver a talk based on that program at <a
href="https://munihac.de/2022.html">this year’s MuniHac</a> and I wanted
to package it up so that other people could collaborate on the program
with me during the hackathon.</p>
<p>When I began writing this post, there was no packaging logic for this
program; it’s a standalone Haskell file. However, this file has several
dependencies outside of Haskell’s standard library, so up until now I
needed some way to obtain those dependencies for development.</p>
<h4 id="stage-0-ghc.withpackages">Stage 0:
<code>ghc.withPackages</code></h4>
<p>The most low-tech way that you can hack on a Haskell program using
Nix is to use <code>nix-shell</code> to obtain a transient development
environment (this is what I had done up until now).</p>
<p>Specifically, you can do something like this:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell <span class="at">--packages</span> <span class="st">'ghc.withPackages (pkgs: [ pkgs.mtl pkgs.MemoTrie pkgs.containers pkgs.pretty-show ])'</span></span></code></pre></div>
<p>… where <code>pkgs.mtl</code> and <code>pkgs.MemoTrie</code> indicate
that I want to include the <code>mtl</code> and <code>MemoTrie</code>
packages in my Haskell development environment.</p>
<p>Inside of that development environment I can build and run the file
using <code>ghc</code>. For example, I can use <code>ghc -O</code> to
build an executable to run:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">[nix-shell]$</span> ghc <span class="at">-O</span> Spire.hs</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">[nix-shell]$</span> ./Spire</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span></code></pre></div>
<p>… or if I don’t care about optimizations I can interpret the file
using <code>runghc</code>:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> runghc Spire.hs</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span></code></pre></div>
<h4 id="stage-1-ide-support">Stage 1: IDE support</h4>
<p>Once I’m inside a Nix shell I can begin to take advantage of
integrated development environment (IDE) support.</p>
<p>The two most common tools Haskell developers use for rapid feedback
are <code>ghcid</code> and <code>haskell-language-server</code>:</p>
<ul>
<li><p><code>ghcid</code> provides a command-line interface for fast
type-checking feedback but doesn’t provide other IDE-like
features</p></li>
<li><p><code>haskell-language-server</code> is more of a proper IDE that
you use in conjunction with some editor</p></li>
</ul>
<p>I can obtain either tool by exiting from the shell and creating a new
shell that includes the desired tool.</p>
<p>For example, if I want to use <code>ghcid</code> then I recreate the
<code>nix-shell</code> using the following command:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell <span class="at">--packages</span> ghcid <span class="st">'ghc.withPackages (pkgs: [ pkgs.mtl pkgs.MemoTrie pkgs.containers pkgs.pretty-show ])'</span></span></code></pre></div>
<p>… and then I can tell <code>ghcid</code> to continuously type-check
my file using:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">[nix-shell]$</span> ghcid Spire.hs</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span></code></pre></div>
<p>If I want to use <code>haskell-language-server</code>, then I
recreate the <code>nix-shell</code> using this command:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell <span class="at">--packages</span> haskell-language-server <span class="st">'ghc.withPackages (pkgs: [ pkgs.mtl pkgs.MemoTrie pkgs.containers pkgs.pretty-show ])'</span></span></code></pre></div>
<p>… and then I can explore the code in any editor that supports the
language server protocol.</p>
<p>Note that if you use VSCode as your editor then you may need to
install some additional plugins:</p>
<ul>
<li><a
href="https://marketplace.visualstudio.com/items?itemName=justusadam.language-haskell">Haskell
Syntax Highlighting</a> plugin</li>
<li><a
href="https://marketplace.visualstudio.com/items?itemName=haskell.haskell">Haskell</a>
plugin</li>
</ul>
<p>… and the next section will show how to install VSCode and those
plugins using Nix.</p>
<p>However, once you do install those plugins then you can open the file
in VSCode from within the <code>nix-shell</code> using:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">[nix-shell]$</span> code Spire.hs</span></code></pre></div>
<p>… and once you <a
href="https://code.visualstudio.com/docs/editor/workspace-trust">trust
the file</a> the IDE features will kick in.</p>
<h4 id="stage-2-global-development-environment">Stage 2: Global
development environment</h4>
<p>Sometimes I like to globally install development tools that are
commonly shared between projects. For example, if I use
<code>ghcid</code> or <code>haskell-language-server</code> across all my
projects then I don’t want to have to explicitly enumerate that tool in
each project’s Nix shell.</p>
<p>Moreover, my tool preferences might not be shared by other
developers. If I share my <code>nix-shell</code> with other developers
for a project then I probably don’t want to add editors/IDEs or other
command-line tools to that environment because then they have to
download those tools regardless of whether they plan to use them.</p>
<p>However, I don’t want to globally install development tools like
this:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-env <span class="at">--install</span> <span class="at">--file</span> <span class="st">'<nixpkgs>'</span> <span class="at">--attr</span> ghcid</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-env <span class="at">--install</span> <span class="at">--file</span> <span class="st">'<nixpkgs>'</span> <span class="at">--attr</span> haskell-language-server</span></code></pre></div>
<p>Part of the reason I use Nix is to avoid imperatively managing my
development environment. Fortunately, though, <code>nix-env</code>
supports a more declarative way of managing dependencies.</p>
<p>What you can do instead is save a file like this to
<code>~/default.nix</code>:</p>
<pre class="nix"><code>let
# For VSCode
config = { allowUnfree = true; };
overlay = pkgsNew: pkgsOld: {
# Here's an example of how to use Nix to install VSCode with plugins managed
# by Nix, too
vscode-with-extensions = pkgsOld.vscode-with-extensions.override {
vscodeExtensions = [
pkgsNew.vscode-extensions.haskell.haskell
pkgsNew.vscode-extensions.justusadam.language-haskell
];
};
};
pkgs = import <nixpkgs> { inherit config; overlays = [ overlay ]; };
in
{ inherit (pkgs)
# I included some sample useful development tools for Haskell. Feel free
# to customize.
cabal-install
ghcid
haskell-language-server
stylish-haskell
vscode-with-extensions
;
} </code></pre>
<p>… and once you create that file you have two options.</p>
<p>The first option is that you can set your global development environment to match
the file by running:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-env <span class="at">--remove-all</span> <span class="at">--install</span> <span class="at">--file</span> ~/default.nix</span></code></pre></div>
<blockquote>
<p>NOTE: At the time of this writing you may also need to add
<code>--system x86_64-darwin</code> if you are trying out these examples
on an M1 Macbook. For more details, see:</p>
<ul>
<li><a
href="https://evanrelf.com/building-x86-64-packages-with-nix-on-apple-silicon">Building
x86-64 Packages With Nix on Apple Silicon</a></li>
</ul>
</blockquote>
<p>Carefully note the <code>--remove-all</code>, which resets your
development environment to match the file, so that nothing from your old
development environment is accidentally carried over into your new
development environment. This makes our use of the <code>nix-env</code>
command truly declarative.</p>
<p>The second option is that you can change the file to create a valid
shell, like this:</p>
<pre class="nix"><code>let
config = { allowUnfree = true; };
overlay = pkgsNew: pkgsOld: {
vscode-with-extensions = pkgsOld.vscode-with-extensions.override {
vscodeExtensions = [
pkgsNew.vscode-extensions.haskell.haskell
pkgsNew.vscode-extensions.justusadam.language-haskell
];
};
};
pkgs = import <nixpkgs> { inherit config; overlays = [ overlay ]; };
in
pkgs.mkShell {
packages = [
pkgs.ghcid
pkgs.haskell-language-server
pkgs.stylish-haskell
pkgs.vscode-with-extensions
pkgs.cabal-install
];
}</code></pre>
<p>… and then run:</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell ~/default.nix</span></code></pre></div>
<p>Or, even better, you can rename the file to <code>~/shell.nix</code>
and then if you’re already in your home directory (e.g. you just logged
into your system), then you can run:</p>
<div class="sourceCode" id="cb13"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell</span></code></pre></div>
<p>… which will select <code>~/shell.nix</code> by default. This lets
you get a completely transient development environment so that you never
have to install anything development tools globally.</p>
<p>These <code>nix-shell</code> commands stack, so you can first run
<code>nix-shell</code> to obtain your global development environment and
then use <code>nix-shell</code> a second time to obtain project-specific
dependencies.</p>
<p>My personal preference is to use the declarative <code>nix-env</code>
trick for installing global development tools. In my opinion it’s just
as elegant as <code>nix-shell</code> and slightly less hassle.</p>
<h4 id="stage-3-cabal">Stage 3: Cabal</h4>
<p>Anyway, enough about global development tools. Back to our Haskell
project!</p>
<p>So <code>ghc.withPackages</code> is a great way to just start hacking
on a standalone Haskell program when you don’t want to worry about
packaging up the program. However, at some point you might want to share
the program with the others or do a proper job of packaging if you’re
trying to <a
href="https://en.wiktionary.org/wiki/productionize">productionize</a>
the code.</p>
<p>That brings us to the next step, which is packaging our Haskell
program with a Cabal file (a Haskell package manifest). We’ll need the
<code>cabal-install</code> command-line tool before we proceed further,
so you’ll want to add that tool to your global development environment
(see the previous section).</p>
<p>To create our <code>.cabal</code> file we can run the following
command from the top-level directory of our Haskell project:</p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cabal init <span class="at">--interactive</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="ex">Should</span> I generate a simple project with sensible defaults<span class="pp">?</span> [default: y] n</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span></code></pre></div>
<p>… and follow the prompts to create a starting point for our
<code>.cabal</code> file.</p>
<p>After completing those choices and trimming down the
<code>.cabal</code> file (to keep the example simple), I get a file that
looks like this:</p>
<pre class="cabal"><code>cabal-version: 2.4
name: spire
version: 1.0.0
license: BSD-3-Clause
license-file: LICENSE
executable spire
main-is: Spire.hs
build-depends: base ^>=4.14.3.0
default-language: Haskell2010</code></pre>
<p>The only thing I’m going change for now is to add dependencies to the
<code>build-depends</code> section and increase the upper bound on
<code>base</code>::</p>
<pre class="cabal"><code>cabal-version: 2.4
name: spire
version: 1.0.0
license: BSD-3-Clause
license-file: LICENSE
executable spire
main-is: Spire.hs
build-depends: base >=4.14.3.0 && < 5
, MemoTrie
, containers
, mtl
, pretty-show
, transformers
default-language: Haskell2010</code></pre>
<h4 id="stage-4-cabal2nix---shell">Stage 4:
<code>cabal2nix --shell</code></h4>
<p>Adding a <code>.cabal</code> file suffices to share our Haskell
package with other Haskell developers if they’re not using Nix. However,
if we want to Nix-enable package our package then we have a few
options.</p>
<p>The simplest option is to run the following command from the
top-level of the Haskell project:</p>
<pre><code>$ cabal2nix --shell . > shell.nix</code></pre>
<p>That will create something similar to the following
<code>shell.nix</code> file:</p>
<pre class="nix"><code>{ nixpkgs ? import <nixpkgs> {}, compiler ? "default", doBenchmark ? false }:
let
inherit (nixpkgs) pkgs;
f = { mkDerivation, base, containers, lib, MemoTrie, mtl
, pretty-show, transformers
}:
mkDerivation {
pname = "spire";
version = "1.0.0";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [
base containers MemoTrie mtl pretty-show transformers
];
license = lib.licenses.bsd3;
};
haskellPackages = if compiler == "default"
then pkgs.haskellPackages
else pkgs.haskell.packages.${compiler};
variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id;
drv = variant (haskellPackages.callPackage f {});
in
if pkgs.lib.inNixShell then drv.env else drv</code></pre>
<p>… and if you run <code>nix-shell</code> within the same directory the
shell environment will have the Haskell dependencies you need to build
and run project using <code>cabal</code>:</p>
<div class="sourceCode" id="cb19"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell</span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a><span class="ex">[nix-shell]$</span> cabal run</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span></span></code></pre></div>
<p>… and tools like <code>ghcid</code> and
<code>haskell-language-server</code> will also work within this shell,
too. The only difference is that <code>ghcid</code> now takes no
arguments, since it will auto-detect the cabal project in the current
directory:</p>
<div class="sourceCode" id="cb20"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="ex">[nix-shell]$</span> ghcid</span></code></pre></div>
<p>Note that this <code>nix-shell</code> will <strong>NOT</strong>
include <code>cabal</code> by default. You will need to globally install
<code>cabal</code> (see the prior section on “Global development
environment”).</p>
<p>This <code>cabal2nix --shell</code> workflow is sufficiently
lightweight that you can Nixify other people’s projects on the fly when
hacking on them locally. A common thing I do if I need to make a change
to a person’s project is to clone their repository, run:</p>
<div class="sourceCode" id="cb21"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cabal2nix <span class="at">--shell</span> . <span class="op">></span> shell.nix</span>
<span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell</span></code></pre></div>
<p>… and start hacking away. I don’t even need to upstream the
<code>shell.nix</code> file I created in this way; I just keep it around
locally for my own hacking.</p>
<p>In fact, I typically don’t want to upstream such a
<code>shell.nix</code> file (even if the upstream author were receptive
to Nix), because there are more robust Nix expressions we can upstream
instead.</p>
<h4 id="stage-5-custom-shell.nix-file">Stage 5: Custom
<code>shell.nix</code> file</h4>
<p>One disadvantage of <code>cabal2nix --shell</code> is that you have
to re-run the command any time your dependencies change. However, if
you’re willing to hand-write your own <code>shell.nix</code> file then
you can create something more stable:</p>
<pre class="nix"><code>let
overlay = pkgsNew: pkgsOld: {
haskellPackages = pkgsOld.haskellPackages.override (old: {
overrides = pkgsNew.haskell.lib.packageSourceOverrides {
spire = ./.;
};
});
};
pkgs = import <nixpkgs> { overlays = [ overlay ]; };
in
pkgs.haskellPackages.spire.env</code></pre>
<p>The <code>packageSourceOverrides</code> is the key bit. Under the
hood, that essentially runs <code>cabal2nix</code> for you any time your
project changes and then generates your development environment from the
result. You can also use <code>packageSourceOverrides</code> to specify
non-default versions of dependencies, too:</p>
<pre class="nix"><code>let
overlay = pkgsNew: pkgsOld: {
haskellPackages = pkgsOld.haskellPackages.override (old: {
overrides = pkgsNew.haskell.lib.packageSourceOverrides {
spire = ./.;
# Example of how to pin a dependency to a non-defaul version
pretty-show = "1.9.5";
};
});
};
pkgs = import <nixpkgs> { overlays = [ overlay ]; };
in
pkgs.haskellPackages.spire.env</code></pre>
<p>… although that will only work for packages that have been released
prior to the version of Nixpkgs that you’re depending on.</p>
<p>If you want something a bit more robust, you can do something like
this:</p>
<pre class="nix"><code>let
overlay = pkgsNew: pkgsOld: {
haskellPackages = pkgsOld.haskellPackages.override (old: {
overrides =
pkgsNew.lib.fold
pkgsNew.lib.composeExtensions
(old.overrides or (_: _: { }))
[ (pkgsNew.haskell.lib.packageSourceOverrides {
spire = ./.;
})
(pkgsNew.haskell.lib.packagesFromDirectory {
directory = ./packages;
})
];
});
};
pkgs = import <nixpkgs> { overlays = [ overlay ]; };
in
pkgs.haskellPackages.spire.env</code></pre>
<p>… and then you have the option to also depend on any dependency that
<code>cabal2nix</code> knows how to generate:</p>
<div class="sourceCode" id="cb25"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> mkdir packages</span>
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> <span class="co"># Add the following file to version control to preserve the directory</span></span>
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> touch packages/.gitkeep</span>
<span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb25-6"><a href="#cb25-6" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cabal update</span>
<span id="cb25-7"><a href="#cb25-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb25-8"><a href="#cb25-8" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cabal2nix cabal://<span class="va">${PACKAGE_NAME}</span>-<span class="va">${VERSION}</span> <span class="op">></span> ./packages/<span class="va">${PACKAGE_NAME}</span>.nix</span></code></pre></div>
<p>… and that works even on bleeding-edge Haskell packages that Nixpkgs
hasn’t picked up, yet.</p>
<h4 id="stage-6-pinning-nixpkgs">Stage 6: Pinning Nixpkgs</h4>
<p>All of the prior examples are “impure”, meaning that they depend on
the ambient <code>nixpkgs</code> channel installed on the developer’s
system. This <code>nixpkgs</code> channel might vary from system to
system, meaning that each system might have different versions of
<code>nixpkgs</code> installed, and then you run into issues reproducing
each other’s builds.</p>
<p>For example, if you have a newer version of <code>nixpkgs</code>
installed your Nix build for the above Haskell project might succeed,
but then another developer might attempt to build your project with an
older version of <code>nixpkgs</code>, which might select an older
incompatible version of one of your Haskell dependencies.</p>
<p>Or, vice versa, the examples in this blog post might succeed at the
time of this writing for the current version of <code>nixpkgs</code> but
then as time goes on the examples might begin to fail for future
versions of <code>nixpkgs</code>.</p>
<p>You can fix that by pinning Nixpkgs, which this post covers:</p>
<ul>
<li><a
href="https://nixos.wiki/wiki/How_to_fetch_Nixpkgs_with_an_empty_NIX_PATH">How
to fetch Nixpkgs with an empty <code>NIX_PATH</code></a></li>
</ul>
<p>For example, we could pin <code>nixpkgs</code> for our global
<code>~/default.nix</code> like this:</p>
<pre class="nix"><code>let
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08.tar.gz";
sha256 = "14ann7vz7qgfrw39ji1s19n1p0likyf2ag8h7rh8iwp3iv5lmprl";
};
config = { allowUnfree = true; };
overlay = pkgsNew: pkgsOld: {
vscode-with-extensions = pkgsOld.vscode-with-extensions.override {
vscodeExtensions = [
pkgsNew.vscode-extensions.haskell.haskell
pkgsNew.vscode-extensions.justusadam.language-haskell
];
};
};
pkgs = import nixpkgs { inherit config; overlays = [ overlay ]; };
in
{ inherit (pkgs)
cabal-install
ghcid
haskell-language-server
stylish-haskell
vscode-with-extensions
;
}</code></pre>
<p>… which pins us to the tip of the <code>release-22.05</code> branch
at the time of this writing.</p>
<p>We can likewise pin <code>nixpkgs</code> for our project-local
<code>shell.nix</code> like this:</p>
<pre class="nix"><code>let
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08.tar.gz";
sha256 = "14ann7vz7qgfrw39ji1s19n1p0likyf2ag8h7rh8iwp3iv5lmprl";
};
overlay = pkgsNew: pkgsOld: {
haskellPackages = pkgsOld.haskellPackages.override (old: {
overrides = pkgsNew.haskell.lib.packageSourceOverrides {
spire = ./.;
};
});
};
pkgs = import nixpkgs { overlays = [ overlay ]; };
in
pkgs.haskellPackages.spire.env</code></pre>
<h4 id="flakes">Flakes</h4>
<p>The final improvement we can make is the most important one of all:
we can convert our project into a Nix flake:</p>
<ul>
<li><a href="https://nixos.wiki/wiki/Flakes">NixOS Wiki -
Flakes</a></li>
</ul>
<p>There are two main motivations for flake-enabling our project:</p>
<ul>
<li>To simplify managing inputs that we need to lock
(e.g. <code>nixpkgs</code>)</li>
<li>To speed up our shell</li>
</ul>
<p>To flake-enable our project, we’ll save the following code to
<code>flake.nix</code>:</p>
<pre class="nix"><code>{ inputs = {
nixpkgs.url = github:NixOS/nixpkgs/release-22.05;
utils.url = github:numtide/flake-utils;
};
outputs = { nixpkgs, utils, ... }:
utils.lib.eachDefaultSystem (system:
let
config = { };
overlay = pkgsNew: pkgsOld: {
spire =
pkgsNew.haskell.lib.justStaticExecutables
pkgsNew.haskellPackages.spire;
haskellPackages = pkgsOld.haskellPackages.override (old: {
overrides = pkgsNew.haskell.lib.packageSourceOverrides {
spire = ./.;
};
});
};
pkgs =
import nixpkgs { inherit config system; overlays = [ overlay ]; };
in
rec {
packages.default = pkgs.haskellPackages.spire;
apps.default = {
type = "app";
program = "${pkgs.spire}/bin/spire";
};
devShells.default = pkgs.haskellPackages.spire.env;
}
);
}</code></pre>
<p>… and then we can delete our old <code>shell.nix</code> because we
don’t need it anymore.</p>
<p>Now we can obtain a development environment by running:</p>
<div class="sourceCode" id="cb29"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix develop</span></code></pre></div>
<p>… and the above flake also makes it possible to easily build and run
the program, too:</p>
<div class="sourceCode" id="cb30"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix run <span class="co"># Run the program</span></span>
<span id="cb30-2"><a href="#cb30-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build <span class="co"># Build the project</span></span></code></pre></div>
<p>In fact, you can even run a flake without having to clone a
repository. For example, you can run the example code from this blog
post by typing:</p>
<div class="sourceCode" id="cb31"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb31-1"><a href="#cb31-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix run github:Gabriella439/spire</span></code></pre></div>
<p>Moreover, we no longer have to take care of managing hashes for, say,
Nixpkgs. The flake machinery takes care of that automatically for you
and generates a <code>flake.lock</code> file which you can then add to
version control. For example, the lock file I got was:</p>
<div class="sourceCode" id="cb32"><pre
class="sourceCode json"><code class="sourceCode json"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">"nodes"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-3"><a href="#cb32-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">"nixpkgs"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-4"><a href="#cb32-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">"locked"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-5"><a href="#cb32-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">"lastModified"</span><span class="fu">:</span> <span class="dv">1661617163</span><span class="fu">,</span></span>
<span id="cb32-6"><a href="#cb32-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">"narHash"</span><span class="fu">:</span> <span class="st">"sha256-NN9Ky47j8ohgPhA9JZyfkYIbbAo6RJkGz+7h8/exVpE="</span><span class="fu">,</span></span>
<span id="cb32-7"><a href="#cb32-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">"owner"</span><span class="fu">:</span> <span class="st">"NixOS"</span><span class="fu">,</span></span>
<span id="cb32-8"><a href="#cb32-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">"repo"</span><span class="fu">:</span> <span class="st">"nixpkgs"</span><span class="fu">,</span></span>
<span id="cb32-9"><a href="#cb32-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">"rev"</span><span class="fu">:</span> <span class="st">"0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08"</span><span class="fu">,</span></span>
<span id="cb32-10"><a href="#cb32-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">"type"</span><span class="fu">:</span> <span class="st">"github"</span></span>
<span id="cb32-11"><a href="#cb32-11" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb32-12"><a href="#cb32-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">"original"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-13"><a href="#cb32-13" aria-hidden="true" tabindex="-1"></a> <span class="dt">"owner"</span><span class="fu">:</span> <span class="st">"NixOS"</span><span class="fu">,</span></span>
<span id="cb32-14"><a href="#cb32-14" aria-hidden="true" tabindex="-1"></a> <span class="dt">"ref"</span><span class="fu">:</span> <span class="st">"release-22.05"</span><span class="fu">,</span></span>
<span id="cb32-15"><a href="#cb32-15" aria-hidden="true" tabindex="-1"></a> <span class="dt">"repo"</span><span class="fu">:</span> <span class="st">"nixpkgs"</span><span class="fu">,</span></span>
<span id="cb32-16"><a href="#cb32-16" aria-hidden="true" tabindex="-1"></a> <span class="dt">"type"</span><span class="fu">:</span> <span class="st">"github"</span></span>
<span id="cb32-17"><a href="#cb32-17" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb32-18"><a href="#cb32-18" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb32-19"><a href="#cb32-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">"root"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-20"><a href="#cb32-20" aria-hidden="true" tabindex="-1"></a> <span class="dt">"inputs"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-21"><a href="#cb32-21" aria-hidden="true" tabindex="-1"></a> <span class="dt">"nixpkgs"</span><span class="fu">:</span> <span class="st">"nixpkgs"</span><span class="fu">,</span></span>
<span id="cb32-22"><a href="#cb32-22" aria-hidden="true" tabindex="-1"></a> <span class="dt">"utils"</span><span class="fu">:</span> <span class="st">"utils"</span></span>
<span id="cb32-23"><a href="#cb32-23" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb32-24"><a href="#cb32-24" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb32-25"><a href="#cb32-25" aria-hidden="true" tabindex="-1"></a> <span class="dt">"utils"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-26"><a href="#cb32-26" aria-hidden="true" tabindex="-1"></a> <span class="dt">"locked"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-27"><a href="#cb32-27" aria-hidden="true" tabindex="-1"></a> <span class="dt">"lastModified"</span><span class="fu">:</span> <span class="dv">1659877975</span><span class="fu">,</span></span>
<span id="cb32-28"><a href="#cb32-28" aria-hidden="true" tabindex="-1"></a> <span class="dt">"narHash"</span><span class="fu">:</span> <span class="st">"sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc="</span><span class="fu">,</span></span>
<span id="cb32-29"><a href="#cb32-29" aria-hidden="true" tabindex="-1"></a> <span class="dt">"owner"</span><span class="fu">:</span> <span class="st">"numtide"</span><span class="fu">,</span></span>
<span id="cb32-30"><a href="#cb32-30" aria-hidden="true" tabindex="-1"></a> <span class="dt">"repo"</span><span class="fu">:</span> <span class="st">"flake-utils"</span><span class="fu">,</span></span>
<span id="cb32-31"><a href="#cb32-31" aria-hidden="true" tabindex="-1"></a> <span class="dt">"rev"</span><span class="fu">:</span> <span class="st">"c0e246b9b83f637f4681389ecabcb2681b4f3af0"</span><span class="fu">,</span></span>
<span id="cb32-32"><a href="#cb32-32" aria-hidden="true" tabindex="-1"></a> <span class="dt">"type"</span><span class="fu">:</span> <span class="st">"github"</span></span>
<span id="cb32-33"><a href="#cb32-33" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb32-34"><a href="#cb32-34" aria-hidden="true" tabindex="-1"></a> <span class="dt">"original"</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb32-35"><a href="#cb32-35" aria-hidden="true" tabindex="-1"></a> <span class="dt">"owner"</span><span class="fu">:</span> <span class="st">"numtide"</span><span class="fu">,</span></span>
<span id="cb32-36"><a href="#cb32-36" aria-hidden="true" tabindex="-1"></a> <span class="dt">"repo"</span><span class="fu">:</span> <span class="st">"flake-utils"</span><span class="fu">,</span></span>
<span id="cb32-37"><a href="#cb32-37" aria-hidden="true" tabindex="-1"></a> <span class="dt">"type"</span><span class="fu">:</span> <span class="st">"github"</span></span>
<span id="cb32-38"><a href="#cb32-38" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb32-39"><a href="#cb32-39" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb32-40"><a href="#cb32-40" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb32-41"><a href="#cb32-41" aria-hidden="true" tabindex="-1"></a> <span class="dt">"root"</span><span class="fu">:</span> <span class="st">"root"</span><span class="fu">,</span></span>
<span id="cb32-42"><a href="#cb32-42" aria-hidden="true" tabindex="-1"></a> <span class="dt">"version"</span><span class="fu">:</span> <span class="dv">7</span></span>
<span id="cb32-43"><a href="#cb32-43" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>… and you can easily upgrade to, say, a newer revision of Nixpkgs if
you need to.</p>
<p>Additionally, all of the Nix commands are now <em>faster</em>.
Specifically, the first time you run a command Nix still needs to
download and/or build dependencies, but subsequent runs are faster
because Nix can skip the instantiation phase. For more details, see:</p>
<ul>
<li><a href="https://www.tweag.io/blog/2020-06-25-eval-cache/">Nix
Flakes, Part 2: Evaluation Caching</a></li>
</ul>
<h4 id="conclusion">Conclusion</h4>
<p>Flakes are our final destination, so that’s as far as this post will
go. There are technically some more ways that we can overengineer
things, but in my experience the idioms highlighted in this post are the
ones that provide the highest power-to-weight ratio.</p>
<p>The key thing to take away is that the Nixpkgs Haskell infrastructure
lets you smoothly transition from simpler approaches to more powerful
approaches, and even the final flake-enabled approach is actually not
that complicated.</p>
</body>
</html>Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com4tag:blogger.com,1999:blog-1777990983847811806.post-8103726233121477822022-06-26T10:03:00.004-07:002022-06-26T19:03:59.290-07:00defaultable-map: An Applicative wrapper for Maps<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="defaultable-map: An Applicative wrapper for Maps" />
<meta name="twitter:description" content="An announcement post for a Haskell package for default-valued Maps." />
<title>defaultable-map: An Applicative wrapper for Maps</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>I’m announcing a small utility Haskell package I created that can wrap arbitrary <code>Map</code>-like types to provide <code>Applicative</code> and <code>Alternative</code> instances. You can find this package on Hackage here:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/defaultable-map"><code>defaultable-map</code>: Applicative maps</a></li>
</ul>
<p>I can motivate why the <code>Applicative</code> and <code>Alternative</code> instances matter with a small example. Suppose that I define the following three <code>Map</code>s which are sort of like database tables:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Defaultable.Map</span> </span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ot">firstNames ::</span> <span class="dt">Defaultable</span> (<span class="dt">Map</span> <span class="dt">Int</span>) <span class="dt">String</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>firstNames <span class="ot">=</span> fromList [(<span class="dv">0</span>, <span class="st">"Gabriella"</span>), (<span class="dv">1</span>, <span class="st">"Oscar"</span>), (<span class="dv">2</span>, <span class="st">"Edgar"</span>)]</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ot">lastNames ::</span> <span class="dt">Defaultable</span> (<span class="dt">Map</span> <span class="dt">Int</span>) <span class="dt">String</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>lastNames <span class="ot">=</span> fromList [(<span class="dv">0</span>, <span class="st">"Gonzalez"</span>), (<span class="dv">2</span>, <span class="st">"Codd"</span>), (<span class="dv">3</span>, <span class="st">"Bryant"</span>)]</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="ot">handles ::</span> <span class="dt">Defaultable</span> (<span class="dt">Map</span> <span class="dt">Int</span>) <span class="dt">String</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>handles <span class="ot">=</span> fromList [(<span class="dv">0</span>, <span class="st">"GabriellaG439"</span>), (<span class="dv">1</span>, <span class="st">"posco"</span>), (<span class="dv">3</span>, <span class="st">"avibryant"</span>)]</span></code></pre></div>
<p>If you squint, you can think of these as analogous to database tables, where the primary key is an <code>Int</code> index:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode sql"><code class="sourceCode sql"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">CREATE</span> <span class="kw">TABLE</span> firstNames (<span class="kw">id</span> <span class="dt">integer</span>, firstName text);</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> firstNames (<span class="kw">id</span>, firstName) <span class="kw">VALUES</span> (<span class="dv">0</span>, <span class="st">'Gabriella'</span>);</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> firstNames (<span class="kw">id</span>, firstName) <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="st">'Oscar'</span>);</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> firstNames (<span class="kw">id</span>, firstName) <span class="kw">VALUES</span> (<span class="dv">2</span>, <span class="st">'Edgar'</span>);</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">SELECT</span> <span class="op">*</span> <span class="kw">FROM</span> firstNames;</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="kw">id</span> | firstName</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="co">---+----------</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="dv">0</span> | Gabriella</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="dv">1</span> | Oscar</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="dv">2</span> | Edgar</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">CREATE</span> <span class="kw">TABLE</span> lastNames (<span class="kw">id</span> <span class="dt">integer</span>, lastName text);</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> lastNames (<span class="kw">id</span>, lastName) <span class="kw">VALUES</span> (<span class="dv">0</span>, <span class="st">'Gonzalez'</span>);</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> lastNames (<span class="kw">id</span>, lastName) <span class="kw">VALUES</span> (<span class="dv">2</span>, <span class="st">'Codd'</span>);</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> lastNames (<span class="kw">id</span>, lastName) <span class="kw">VALUES</span> (<span class="dv">3</span>, <span class="st">'Bryant'</span>);</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">SELECT</span> <span class="op">*</span> <span class="kw">FROM</span> lastNames;</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a><span class="kw">id</span> | lastName</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a><span class="co">---+---------</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a><span class="dv">0</span> | Gonzalez</span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a><span class="dv">2</span> | Codd</span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a><span class="dv">3</span> | Bryant</span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">CREATE</span> <span class="kw">TABLE</span> handles (<span class="kw">id</span> <span class="dt">integer</span>, handle text);</span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> handles (<span class="kw">id</span>, handle) <span class="kw">VALUES</span> (<span class="dv">0</span>, <span class="st">'GabriellaG439'</span>);</span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> handles (<span class="kw">id</span>, handle) <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="st">'posco'</span>);</span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">INSERT</span> <span class="kw">INTO</span> handles (<span class="kw">id</span>, handle) <span class="kw">VALUES</span> (<span class="dv">3</span>, <span class="st">'avibryant'</span>);</span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">SELECT</span> <span class="op">*</span> <span class="kw">FROM</span> handles;</span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a><span class="kw">id</span> | handle</span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a><span class="co">---+--------------</span></span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a><span class="dv">0</span> | GabriellaG439</span>
<span id="cb2-31"><a href="#cb2-31" aria-hidden="true" tabindex="-1"></a><span class="dv">1</span> | posco</span>
<span id="cb2-32"><a href="#cb2-32" aria-hidden="true" tabindex="-1"></a><span class="dv">3</span> | avibryant</span></code></pre></div>
<p>The <code>Defaultable (Map Int)</code> type has a law-abiding <code>Applicative</code> instance, so we can safely “join” these “tables” using <code>Applicative</code> operations. For example, if we enable Haskell’s <code>ApplicativeDo</code> language extension then we can compute an “inner join” on tables like this:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE ApplicativeDo #-}</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ot">innerJoin ::</span> <span class="dt">Defaultable</span> (<span class="dt">Map</span> <span class="dt">Int</span>) (<span class="dt">String</span>, <span class="dt">String</span>)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>innerJoin <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> firstName <span class="ot"><-</span> firstNames</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> lastName <span class="ot"><-</span> lastNames</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (firstName, lastName)</span></code></pre></div>
<p>… and that evaluates to the following result:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Defaultable</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> (fromList</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> [ (<span class="dv">0</span>, (<span class="st">"Gabriella"</span>,<span class="st">"Gonzalez"</span>))</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> , (<span class="dv">2</span>, (<span class="st">"Edgar"</span> ,<span class="st">"Codd"</span> ))</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span></span></code></pre></div>
<p>… which is the same result we would have gotten from doing an inner join in SQL:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode sql"><code class="sourceCode sql"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">SELECT</span> firstNames.<span class="kw">id</span>, firstName, lastName</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">FROM</span> firstNames <span class="kw">INNER</span> <span class="kw">JOIN</span> lastNames <span class="kw">on</span> firstNames.<span class="kw">id</span> <span class="op">=</span> lastNames.<span class="kw">id</span>;</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="kw">id</span> | firstName | lastName</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">---+-----------+---------</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="dv">0</span> | Gabriella | Gonzalez</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="dv">2</span> | Edgar | Codd</span></code></pre></div>
<p>The <code>Defaultable (Map Int)</code> type also has a law-abiding <code>Alternative</code> instance, which we can combine with the <code>Applicative</code> instance to compute “left/right/outer joins”. For example, this “left join”:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ot">leftJoin ::</span> <span class="dt">Defaultable</span> (<span class="dt">Map</span> <span class="dt">Int</span>) (<span class="dt">String</span>, <span class="dt">Maybe</span> <span class="dt">String</span>)</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>leftJoin <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a> firstName <span class="ot"><-</span> firstNames</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> lastName <span class="ot"><-</span> optional lastNames</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (firstName, lastName)</span></code></pre></div>
<p>… evaluates to:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Defaultable</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> (fromList</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> [ (<span class="dv">0</span>, (<span class="st">"Gabriella"</span>,<span class="dt">Just</span> <span class="st">"Gonzalez"</span>))</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> , (<span class="dv">1</span>, (<span class="st">"Oscar"</span> ,<span class="dt">Nothing</span> ))</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> , (<span class="dv">2</span>, (<span class="st">"Edgar"</span> ,<span class="dt">Just</span> <span class="st">"Codd"</span> ))</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span></span></code></pre></div>
<p>… which is analogous to this SQL left join:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode sql"><code class="sourceCode sql"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">SELECT</span> firstNames.<span class="kw">id</span>, firstName, lastName</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">FROM</span> firstNames <span class="kw">LEFT</span> <span class="kw">JOIN</span> lastNames <span class="kw">on</span> firstNames.<span class="kw">id</span> <span class="op">=</span> lastNames.<span class="kw">id</span>;</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="kw">id</span> | firstName | lastName</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="co">---+-----------+---------</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="dv">0</span> | Gabriella | Gonzalez</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="dv">1</span> | Oscar |</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="dv">2</span> | Edgar | Codd</span></code></pre></div>
<p>Since Haskell is a more fully-featured language than SQL, we can do more sophisticated things more easily than in SQL. For example, the following three-way join with some post-processing logic is much easier to express in Haskell than SQL:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ot">display ::</span> <span class="dt">String</span> <span class="ot">-></span> <span class="dt">Maybe</span> <span class="dt">String</span> <span class="ot">-></span> <span class="dt">String</span> <span class="ot">-></span> <span class="dt">String</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>display firstName <span class="dt">Nothing</span> handle <span class="ot">=</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a> firstName <span class="op"><></span> <span class="st">": @"</span> <span class="op"><></span> handle</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>display firstName (<span class="dt">Just</span> lastName) handle <span class="ot">=</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a> firstName <span class="op"><></span> <span class="st">" "</span> <span class="op"><></span> lastName <span class="op"><></span> <span class="st">": @"</span> <span class="op"><></span> handle</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="ot">interestingJoin ::</span> <span class="dt">Defaultable</span> (<span class="dt">Map</span> <span class="dt">Int</span>) <span class="dt">String</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a>interestingJoin <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a> firstName <span class="ot"><-</span> firstNames</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a> lastName <span class="ot"><-</span> optional lastNames</span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a> handle <span class="ot"><-</span> handles</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (display firstName lastName handle)</span></code></pre></div>
<p>… which evaluates to:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Defaultable</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> (fromList</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> [ (<span class="dv">0</span>, <span class="st">"Gabriella Gonzalez: @GabriellaG439"</span>)</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> , (<span class="dv">1</span>, <span class="st">"Oscar: @posco"</span> )</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span></span></code></pre></div>
<h4 id="the-defaultable-type-constructor">The <code>Defaultable</code> type constructor</h4>
<p>The central data type exported by the package is the <code>Defaultable</code> type constructor, which has the following simple definition:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Defaultable</span> <span class="fu">map</span> value <span class="ot">=</span> <span class="dt">Defaultable</span> (<span class="fu">map</span> value) (<span class="dt">Maybe</span> value)</span></code></pre></div>
<p>Here the <code>map</code> type parameter can be any <code>Map</code>-like type that includes the type of the key. For example, a typical instantiation of the <code>Defaultable</code> type constructor might be <code>Defaultable (Map key) value</code> or <code>Defaultable IntMap value</code>.</p>
<p>The first field of the type is the actual map that you want to wrap in order to get an <code>Applicative</code> and <code>Alternative</code> instance. The second field is an optional default value stored alongside the map that can be returned if a <code>lookup</code> does not find a matching key.</p>
<p>The default value is not required (it can be <code>Nothing</code>), but that default value is what makes the <code>Applicative</code> instance work. Specifically, without the ability to specify a default value there would be no way to implement <code>pure</code> for a <code>Map</code>-like type.</p>
<p>In case you’re curious, here is what the <code>Applicative</code> instance looks like:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (<span class="dt">Apply</span> <span class="fu">map</span>, <span class="kw">forall</span> a <span class="op">.</span> <span class="dt">Monoid</span> (<span class="fu">map</span> a)) <span class="ot">=></span> <span class="dt">Applicative</span> (<span class="dt">Defaultable</span> <span class="fu">map</span>)</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> v <span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> v)</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">Defaultable</span> fMap fDefault <span class="op"><*></span> <span class="dt">Defaultable</span> xMap xDefault <span class="ot">=</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> (fMap <span class="op"><.></span> xMap) <span class="op"><></span> fFallback <span class="op"><></span> xFallback</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> fDefault <span class="kw">of</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> f <span class="ot">-></span> <span class="fu">fmap</span> f xMap</span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">fmap</span> (<span class="op">$</span> x) fMap</span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> fDefault <span class="op"><*></span> xDefault</span></code></pre></div>
<p>The neat part of the above instance is the class constraint:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a> ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (<span class="dt">Apply</span> <span class="fu">map</span>, <span class="kw">forall</span> a <span class="op">.</span> <span class="dt">Monoid</span> (<span class="fu">map</span> a)) <span class="ot">=></span> <span class="dt">Applicative</span> (<span class="dt">Defaultable</span> <span class="fu">map</span>)</span></code></pre></div>
<p>The <code>Defaultable</code> type is set up in such a way that you can wrap any <code>Map</code>-like type that satisfies that constraint (which is basically all of them) and get a law-abiding <code>Applicative</code> instance (See the <a href="#appendix---proof-of-the-applicative-laws">Appendix</a> for a proof of the <code>Applicative</code> laws).</p>
<p>In particular, this constraint makes use of <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/quantified_constraints.html">the <code>QuantifiedConstraints</code> language extension</a> introduced in GHC 8.6. Without that instance then we wouldn’t be able to generalize this type to wrap arbitrary <code>Map</code>s and we’d have to hard-code the package to work with a specific <code>Map</code> like <code>Data.Map.Map</code>.</p>
<p>The <code>Defaultable</code> type also implements <code>Alternative</code>, too, although that instance is much simpler:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (<span class="dt">Apply</span> <span class="fu">map</span>, <span class="kw">forall</span> a <span class="op">.</span> <span class="dt">Monoid</span> (<span class="fu">map</span> a)) <span class="ot">=></span> <span class="dt">Alternative</span> (<span class="dt">Defaultable</span> <span class="fu">map</span>) <span class="kw">where</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a> empty <span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> empty</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">Defaultable</span> lMap lDefault <span class="op"><|></span> <span class="dt">Defaultable</span> rMap rDefault <span class="ot">=</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">Defaultable</span> (lMap <span class="op"><></span> rMap) (lDefault <span class="op"><|></span> rDefault)</span></code></pre></div>
<p>This instance is only possible because the <code>Defaultable</code> type constructor doesn’t require the default value to be present. If the default value were required then we could not sensibly define <code>empty</code>.</p>
<h4 id="prior-art">Prior art</h4>
<p>I was surprised that something like this didn’t already exist on Hackage. The closest package I could find was this one:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/total-map"><code>total-map</code>: Finitely represented total maps</a></li>
</ul>
<p>However, that wasn’t exactly what I wanted, because it requires the default value to be present. That means that you can’t implement an <code>Alternative</code> instance for the <code>TMap</code> type from that package and you therefore can’t do things like left/right/outer joins as I mentioned above.</p>
<p>Also, more generally, sometimes you want a <code>Map</code> to have an <code>Applicative</code> instance without having to specify a default value. Requiring the default to always be present is not necessary to implement <code>Applicative</code>.</p>
<p>The other issue I had with that package is that it’s hard-coded to use <code>Data.Map.Map</code> under the hood, whereas I wanted an API that could be used in conjunction with any <code>Map</code>-like type.</p>
<h4 id="conclusion">Conclusion</h4>
<p>The idea for this package originated from a LambdaConf presentation I gave a while ago where I brainstormed what a good “data science” ecosystem for Haskell might look like:</p>
<ul>
<li><a href="https://github.com/Gabriella439/slides/blob/main/lambdaconf/data/data.md#a-higher-level-api">Data science APIs in Haskell - slides</a></li>
</ul>
<p>I sat on this idea for years without publishing anything to Hackage because my original vision was a bit too ambitious and included much more than just an <code>Applicative</code> <code>Map</code> type. However, recently I needed this <code>Applicative</code> <code>Map</code> type, so I settled for publishing a narrower and more focused package to Hackage.</p>
<p>The personal use case I have in mind for this package is no longer data science, but I hope that people interested in building a data science ecosystem for Haskell consider using this package as a building block since I believe it is well-suited for that purpose.</p>
<h4 id="appendix---proof-of-the-applicative-laws">Appendix - Proof of the <code>Applicative</code> laws</h4>
<p>These proofs require a few additional assumptions about the interaction between the <code>Apply</code> and <code>Monoid</code> constraint on the <code>map</code> type parameter to <code>Defaultable</code>. These assumptions hold for <code>Map</code>-like types.</p>
<p>The first assumption is that <code>fmap</code> is a <code>Monoid</code> homomorphism:</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="fu">fmap</span> f <span class="fu">mempty</span> <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a><span class="fu">fmap</span> f (x <span class="op"><></span> y) <span class="ot">=</span> <span class="fu">fmap</span> f x <span class="op"><></span> <span class="fu">fmap</span> f y</span></code></pre></div>
<p>The second assumption is that <code>f <.></code> is a <code>Monoid</code> homorphism:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a>f <span class="op"><.></span> <span class="fu">mempty</span> <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a>f <span class="op"><.></span> (x <span class="op"><></span> y) <span class="ot">=</span> (f <span class="op"><.></span> x) <span class="op"><></span> (f <span class="op"><.></span> y)</span></code></pre></div>
<p>The final assumption is specific to maps, which is:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Given:</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="ot">mf ::</span> <span class="fu">map</span> (a <span class="ot">-></span> b)</span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="ot">mx ::</span> <span class="fu">map</span> a</span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a><span class="ot">kf ::</span> (a <span class="ot">-></span> b) <span class="ot">-></span> c</span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a><span class="ot">kx ::</span> a <span class="ot">-></span> c</span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a> (mf <span class="op"><.></span> mx) <span class="op"><></span> <span class="fu">fmap</span> kf mf <span class="op"><></span> <span class="fu">fmap</span> kx mx</span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (mf <span class="op"><.></span> mx) <span class="op"><></span> <span class="fu">fmap</span> kx mx <span class="op"><></span> <span class="fu">fmap</span> kf mf</span></code></pre></div>
<p>The intuition here is if that <code>map</code> is a <code>Map</code>-like type constructor then we can think of those three expressions as having a set of keys associated with them, such that:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Given:</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="ot">keys ::</span> <span class="fu">map</span> a <span class="ot">-></span> <span class="dt">Set</span> key</span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a>keys (mf <span class="op"><.></span> mx) <span class="ot">=</span> keys (<span class="fu">fmap</span> kf mf) <span class="ot">`intersection`</span> keys (<span class="fu">fmap</span> kx mx)</span></code></pre></div>
<p>So normally the following equality would not be true:</p>
<pre><code> fmap kf mf <> fmap kx mx
= fmap kx mx <> fmap kf mf</code></pre>
<p>… because the result would change if there was a key collision. Then the order in which we union (<code><></code>) the two maps would change the result.</p>
<p>However, if you union yet another map (<code>mf <.> mx</code>) that shadows the colliding keys then result remains the same.</p>
<p>The proof below uses that assumption a bit less formally by just noting that we can commute a union operation if there is a downstream union operation that would shadow any colliding keys that might differ.</p>
<h5 id="proof-of-identity-law">Proof of identity law</h5>
<div class="sourceCode" id="cb20"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> v</span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure v = Defaultable mempty (pure v)</span></span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> <span class="fu">id</span>) <span class="op"><*></span> v</span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- Expand: v = Defaultable xMap xDefault</span></span>
<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> <span class="fu">id</span>) <span class="op"><*></span> <span class="dt">Defaultable</span> xMap xDefault</span>
<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-9"><a href="#cb20-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>)</span></span>
<span id="cb20-10"><a href="#cb20-10" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-11"><a href="#cb20-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-12"><a href="#cb20-12" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> (<span class="fu">mempty</span> <span class="op"><.></span> xMap) <span class="op"><></span> fFallback <span class="op"><></span> xFallback</span>
<span id="cb20-13"><a href="#cb20-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-14"><a href="#cb20-14" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span></span>
<span id="cb20-15"><a href="#cb20-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="kw">of</span></span>
<span id="cb20-16"><a href="#cb20-16" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-17"><a href="#cb20-17" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> f <span class="ot">-></span> <span class="fu">fmap</span> f xMap</span>
<span id="cb20-18"><a href="#cb20-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-19"><a href="#cb20-19" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb20-20"><a href="#cb20-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb20-21"><a href="#cb20-21" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-22"><a href="#cb20-22" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">fmap</span> (<span class="op">$</span> x) <span class="fu">mempty</span></span>
<span id="cb20-23"><a href="#cb20-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-24"><a href="#cb20-24" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> xDefault</span>
<span id="cb20-25"><a href="#cb20-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-26"><a href="#cb20-26" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <.> xMap = mempty</span></span>
<span id="cb20-27"><a href="#cb20-27" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-28"><a href="#cb20-28" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-29"><a href="#cb20-29" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> fFallback <span class="op"><></span> xFallback</span>
<span id="cb20-30"><a href="#cb20-30" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-31"><a href="#cb20-31" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span></span>
<span id="cb20-32"><a href="#cb20-32" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="kw">of</span></span>
<span id="cb20-33"><a href="#cb20-33" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-34"><a href="#cb20-34" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> f <span class="ot">-></span> <span class="fu">fmap</span> f xMap</span>
<span id="cb20-35"><a href="#cb20-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-36"><a href="#cb20-36" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb20-37"><a href="#cb20-37" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb20-38"><a href="#cb20-38" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-39"><a href="#cb20-39" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">fmap</span> (<span class="op">$</span> x) <span class="fu">mempty</span></span>
<span id="cb20-40"><a href="#cb20-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-41"><a href="#cb20-41" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> xDefault</span>
<span id="cb20-42"><a href="#cb20-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-43"><a href="#cb20-43" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify `case pure id of …`</span></span>
<span id="cb20-44"><a href="#cb20-44" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-45"><a href="#cb20-45" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-46"><a href="#cb20-46" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> fFallback <span class="op"><></span> xFallback</span>
<span id="cb20-47"><a href="#cb20-47" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-48"><a href="#cb20-48" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span> <span class="fu">fmap</span> <span class="fu">id</span> xMap</span>
<span id="cb20-49"><a href="#cb20-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-50"><a href="#cb20-50" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb20-51"><a href="#cb20-51" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb20-52"><a href="#cb20-52" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-53"><a href="#cb20-53" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">fmap</span> (<span class="op">$</span> x) <span class="fu">mempty</span></span>
<span id="cb20-54"><a href="#cb20-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-55"><a href="#cb20-55" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> xDefault</span>
<span id="cb20-56"><a href="#cb20-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-57"><a href="#cb20-57" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap id x = x</span></span>
<span id="cb20-58"><a href="#cb20-58" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-59"><a href="#cb20-59" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-60"><a href="#cb20-60" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> fFallback <span class="op"><></span> xFallback</span>
<span id="cb20-61"><a href="#cb20-61" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-62"><a href="#cb20-62" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span> xMap</span>
<span id="cb20-63"><a href="#cb20-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-64"><a href="#cb20-64" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb20-65"><a href="#cb20-65" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb20-66"><a href="#cb20-66" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-67"><a href="#cb20-67" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">fmap</span> (<span class="op">$</span> x) <span class="fu">mempty</span></span>
<span id="cb20-68"><a href="#cb20-68" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-69"><a href="#cb20-69" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> xDefault</span>
<span id="cb20-70"><a href="#cb20-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-71"><a href="#cb20-71" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f mempty = mempty</span></span>
<span id="cb20-72"><a href="#cb20-72" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-73"><a href="#cb20-73" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-74"><a href="#cb20-74" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><</span> <span class="op">></span>fFallback <span class="op"><></span> xFallback</span>
<span id="cb20-75"><a href="#cb20-75" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-76"><a href="#cb20-76" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span> xMap</span>
<span id="cb20-77"><a href="#cb20-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-78"><a href="#cb20-78" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb20-79"><a href="#cb20-79" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb20-80"><a href="#cb20-80" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-81"><a href="#cb20-81" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-82"><a href="#cb20-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-83"><a href="#cb20-83" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> xDefault</span>
<span id="cb20-84"><a href="#cb20-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-85"><a href="#cb20-85" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure id <*> v = v</span></span>
<span id="cb20-86"><a href="#cb20-86" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-87"><a href="#cb20-87" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-88"><a href="#cb20-88" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> fFallback <span class="op"><></span> xFallback</span>
<span id="cb20-89"><a href="#cb20-89" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-90"><a href="#cb20-90" aria-hidden="true" tabindex="-1"></a> fFallback <span class="ot">=</span> xMap</span>
<span id="cb20-91"><a href="#cb20-91" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-92"><a href="#cb20-92" aria-hidden="true" tabindex="-1"></a> xFallback <span class="ot">=</span></span>
<span id="cb20-93"><a href="#cb20-93" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> xDefault <span class="kw">of</span></span>
<span id="cb20-94"><a href="#cb20-94" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-95"><a href="#cb20-95" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb20-96"><a href="#cb20-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-97"><a href="#cb20-97" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> xDefault</span>
<span id="cb20-98"><a href="#cb20-98" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-99"><a href="#cb20-99" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify</span></span>
<span id="cb20-100"><a href="#cb20-100" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-101"><a href="#cb20-101" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-102"><a href="#cb20-102" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> xMap <span class="op"><></span> <span class="fu">mempty</span></span>
<span id="cb20-103"><a href="#cb20-103" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-104"><a href="#cb20-104" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> xDefault</span>
<span id="cb20-105"><a href="#cb20-105" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-106"><a href="#cb20-106" aria-hidden="true" tabindex="-1"></a><span class="co">-- x <> mempty = x</span></span>
<span id="cb20-107"><a href="#cb20-107" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <> x = x</span></span>
<span id="cb20-108"><a href="#cb20-108" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb20-109"><a href="#cb20-109" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb20-110"><a href="#cb20-110" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> xMap</span>
<span id="cb20-111"><a href="#cb20-111" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-112"><a href="#cb20-112" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> xDefault</span>
<span id="cb20-113"><a href="#cb20-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-114"><a href="#cb20-114" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify</span></span>
<span id="cb20-115"><a href="#cb20-115" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> xMap xDefault</span>
<span id="cb20-116"><a href="#cb20-116" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-117"><a href="#cb20-117" aria-hidden="true" tabindex="-1"></a><span class="co">-- Contract: v = Defaultable xMap xDefault</span></span>
<span id="cb20-118"><a href="#cb20-118" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> v</span></code></pre></div>
<h5 id="proof-of-the-composition-law">Proof of the composition law</h5>
<div class="sourceCode" id="cb21"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> (<span class="op">.</span>) <span class="op"><*></span> u <span class="op"><*></span> v <span class="op"><*></span> w</span>
<span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-3"><a href="#cb21-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Expand:</span></span>
<span id="cb21-4"><a href="#cb21-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- u = Defaultable uMap uDefault</span></span>
<span id="cb21-5"><a href="#cb21-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- v = Defaultable vMap vDefault</span></span>
<span id="cb21-6"><a href="#cb21-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- w = Defaultable wMap wDefault</span></span>
<span id="cb21-7"><a href="#cb21-7" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (<span class="op">.</span>)</span>
<span id="cb21-8"><a href="#cb21-8" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> uMap uDefault</span>
<span id="cb21-9"><a href="#cb21-9" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap vDefault</span>
<span id="cb21-10"><a href="#cb21-10" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap wDefault</span>
<span id="cb21-11"><a href="#cb21-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-12"><a href="#cb21-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure v = Defaultable mempty (pure v)</span></span>
<span id="cb21-13"><a href="#cb21-13" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> (<span class="op">.</span>))</span>
<span id="cb21-14"><a href="#cb21-14" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> uMap uDefault</span>
<span id="cb21-15"><a href="#cb21-15" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap vDefault</span>
<span id="cb21-16"><a href="#cb21-16" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap wDefault</span></code></pre></div>
<p>… before continuing, it’s easiest to prove all eight possible combinations of:</p>
<ul>
<li><code>uDefault</code> is <code>pure u</code> or <code>empty</code></li>
<li><code>vDefault</code> is <code>pure v</code> or <code>empty</code></li>
<li><code>wDefault</code> is <code>pure w</code> or <code>empty</code></li>
</ul>
<p>To avoid lots of repetition, I’ll only prove the most difficult case (where all defaults are present), since the other proofs are essentially subsets of that proof where some subterms disappear because they become <code>mempty</code>.</p>
<p>Case:</p>
<ul>
<li><code>uDefault = pure u</code></li>
<li><code>vDefault = pure v</code></li>
<li><code>wDefault = pure w</code></li>
</ul>
<div class="sourceCode" id="cb22"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> (<span class="op">.</span>))</span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> uMap (<span class="fu">pure</span> u)</span>
<span id="cb22-3"><a href="#cb22-3" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-4"><a href="#cb22-4" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-5"><a href="#cb22-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-6"><a href="#cb22-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>)</span></span>
<span id="cb22-7"><a href="#cb22-7" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuMap cuDefault</span>
<span id="cb22-8"><a href="#cb22-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-9"><a href="#cb22-9" aria-hidden="true" tabindex="-1"></a> cuMap <span class="ot">=</span> (<span class="fu">mempty</span> <span class="op"><.></span> uMap) <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> u) <span class="fu">mempty</span></span>
<span id="cb22-10"><a href="#cb22-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-11"><a href="#cb22-11" aria-hidden="true" tabindex="-1"></a> cuDefault <span class="ot">=</span> <span class="fu">pure</span> (<span class="op">.</span>) <span class="op"><*></span> <span class="fu">pure</span> u</span>
<span id="cb22-12"><a href="#cb22-12" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-13"><a href="#cb22-13" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-14"><a href="#cb22-14" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-15"><a href="#cb22-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-16"><a href="#cb22-16" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <.> x = mempty</span></span>
<span id="cb22-17"><a href="#cb22-17" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuMap cuDefault</span>
<span id="cb22-18"><a href="#cb22-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-19"><a href="#cb22-19" aria-hidden="true" tabindex="-1"></a> cuMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> u) <span class="fu">mempty</span></span>
<span id="cb22-20"><a href="#cb22-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-21"><a href="#cb22-21" aria-hidden="true" tabindex="-1"></a> cuDefault <span class="ot">=</span> <span class="fu">pure</span> (<span class="op">.</span>) <span class="op"><*></span> <span class="fu">pure</span> u</span>
<span id="cb22-22"><a href="#cb22-22" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-23"><a href="#cb22-23" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-24"><a href="#cb22-24" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-25"><a href="#cb22-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-26"><a href="#cb22-26" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f mempty = mempty</span></span>
<span id="cb22-27"><a href="#cb22-27" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuMap cuDefault</span>
<span id="cb22-28"><a href="#cb22-28" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-29"><a href="#cb22-29" aria-hidden="true" tabindex="-1"></a> cuMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><></span> <span class="fu">mempty</span></span>
<span id="cb22-30"><a href="#cb22-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-31"><a href="#cb22-31" aria-hidden="true" tabindex="-1"></a> cuDefault <span class="ot">=</span> <span class="fu">pure</span> (<span class="op">.</span>) <span class="op"><*></span> <span class="fu">pure</span> u</span>
<span id="cb22-32"><a href="#cb22-32" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-33"><a href="#cb22-33" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-34"><a href="#cb22-34" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-35"><a href="#cb22-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-36"><a href="#cb22-36" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> pure x = pure (f x)</span></span>
<span id="cb22-37"><a href="#cb22-37" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuMap cuDefault</span>
<span id="cb22-38"><a href="#cb22-38" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-39"><a href="#cb22-39" aria-hidden="true" tabindex="-1"></a> cuMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><></span> <span class="fu">mempty</span></span>
<span id="cb22-40"><a href="#cb22-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-41"><a href="#cb22-41" aria-hidden="true" tabindex="-1"></a> cuDefault <span class="ot">=</span> <span class="fu">pure</span> ((<span class="op">.</span>) u)</span>
<span id="cb22-42"><a href="#cb22-42" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-43"><a href="#cb22-43" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-44"><a href="#cb22-44" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-45"><a href="#cb22-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-46"><a href="#cb22-46" aria-hidden="true" tabindex="-1"></a><span class="co">-- x <> mempty = x</span></span>
<span id="cb22-47"><a href="#cb22-47" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <> x = x</span></span>
<span id="cb22-48"><a href="#cb22-48" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuMap cuDefault</span>
<span id="cb22-49"><a href="#cb22-49" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-50"><a href="#cb22-50" aria-hidden="true" tabindex="-1"></a> cuMap <span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">.</span>) uMap</span>
<span id="cb22-51"><a href="#cb22-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-52"><a href="#cb22-52" aria-hidden="true" tabindex="-1"></a> cuDefault <span class="ot">=</span> <span class="fu">pure</span> ((<span class="op">.</span>) u)</span>
<span id="cb22-53"><a href="#cb22-53" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-54"><a href="#cb22-54" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-55"><a href="#cb22-55" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-56"><a href="#cb22-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-57"><a href="#cb22-57" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify</span></span>
<span id="cb22-58"><a href="#cb22-58" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap) (<span class="fu">pure</span> (u <span class="op">.</span>)))</span>
<span id="cb22-59"><a href="#cb22-59" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-60"><a href="#cb22-60" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-61"><a href="#cb22-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-62"><a href="#cb22-62" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>)</span></span>
<span id="cb22-63"><a href="#cb22-63" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuvMap cuvDefault</span>
<span id="cb22-64"><a href="#cb22-64" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-65"><a href="#cb22-65" aria-hidden="true" tabindex="-1"></a> cuvMap <span class="ot">=</span></span>
<span id="cb22-66"><a href="#cb22-66" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-67"><a href="#cb22-67" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span>) vMap</span>
<span id="cb22-68"><a href="#cb22-68" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> v) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap)</span>
<span id="cb22-69"><a href="#cb22-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-70"><a href="#cb22-70" aria-hidden="true" tabindex="-1"></a> cuvDefault <span class="ot">=</span> <span class="fu">pure</span> (u <span class="op">.</span>) <span class="op"><*></span> <span class="fu">pure</span> v</span>
<span id="cb22-71"><a href="#cb22-71" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-72"><a href="#cb22-72" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-73"><a href="#cb22-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-74"><a href="#cb22-74" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) x</span></span>
<span id="cb22-75"><a href="#cb22-75" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuvMap cuvDefault</span>
<span id="cb22-76"><a href="#cb22-76" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-77"><a href="#cb22-77" aria-hidden="true" tabindex="-1"></a> cuvMap <span class="ot">=</span></span>
<span id="cb22-78"><a href="#cb22-78" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-79"><a href="#cb22-79" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span>) vMap</span>
<span id="cb22-80"><a href="#cb22-80" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span> v) uMap</span>
<span id="cb22-81"><a href="#cb22-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-82"><a href="#cb22-82" aria-hidden="true" tabindex="-1"></a> cuvDefault <span class="ot">=</span> <span class="fu">pure</span> (u <span class="op">.</span>) <span class="op"><*></span> <span class="fu">pure</span> v</span>
<span id="cb22-83"><a href="#cb22-83" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-84"><a href="#cb22-84" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-85"><a href="#cb22-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-86"><a href="#cb22-86" aria-hidden="true" tabindex="-1"></a><span class="co">-- ((.) u) = \v -> u . v</span></span>
<span id="cb22-87"><a href="#cb22-87" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuvMap cuvDefault</span>
<span id="cb22-88"><a href="#cb22-88" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-89"><a href="#cb22-89" aria-hidden="true" tabindex="-1"></a> cuvMap <span class="ot">=</span></span>
<span id="cb22-90"><a href="#cb22-90" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-91"><a href="#cb22-91" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span>) vMap</span>
<span id="cb22-92"><a href="#cb22-92" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span> v) uMap</span>
<span id="cb22-93"><a href="#cb22-93" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-94"><a href="#cb22-94" aria-hidden="true" tabindex="-1"></a> cuvDefault <span class="ot">=</span> <span class="fu">pure</span> (u <span class="op">.</span>) <span class="op"><*></span> <span class="fu">pure</span> v</span>
<span id="cb22-95"><a href="#cb22-95" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-96"><a href="#cb22-96" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-97"><a href="#cb22-97" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-98"><a href="#cb22-98" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> pure x = pure (f x)</span></span>
<span id="cb22-99"><a href="#cb22-99" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> (<span class="dt">Defaultable</span> cuvMap cuvDefault</span>
<span id="cb22-100"><a href="#cb22-100" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-101"><a href="#cb22-101" aria-hidden="true" tabindex="-1"></a> cuvMap <span class="ot">=</span></span>
<span id="cb22-102"><a href="#cb22-102" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-103"><a href="#cb22-103" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span>) vMap</span>
<span id="cb22-104"><a href="#cb22-104" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span> v) uMap</span>
<span id="cb22-105"><a href="#cb22-105" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-106"><a href="#cb22-106" aria-hidden="true" tabindex="-1"></a> cuvDefault <span class="ot">=</span> <span class="fu">pure</span> (u <span class="op">.</span> v)</span>
<span id="cb22-107"><a href="#cb22-107" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-108"><a href="#cb22-108" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-109"><a href="#cb22-109" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-110"><a href="#cb22-110" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>)</span></span>
<span id="cb22-111"><a href="#cb22-111" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-112"><a href="#cb22-112" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-113"><a href="#cb22-113" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span> (cuvMap <span class="op"><.></span> wMap) <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) cuvMap </span>
<span id="cb22-114"><a href="#cb22-114" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-115"><a href="#cb22-115" aria-hidden="true" tabindex="-1"></a> cuvMap <span class="ot">=</span></span>
<span id="cb22-116"><a href="#cb22-116" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-117"><a href="#cb22-117" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span>) vMap</span>
<span id="cb22-118"><a href="#cb22-118" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">.</span> v) uMap</span>
<span id="cb22-119"><a href="#cb22-119" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-120"><a href="#cb22-120" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u <span class="op">.</span> v) <span class="op"><*></span> <span class="fu">pure</span> v</span>
<span id="cb22-121"><a href="#cb22-121" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-122"><a href="#cb22-122" aria-hidden="true" tabindex="-1"></a><span class="co">-- (f <> g) <.> x = (f <.> x) <> (g <.> x)</span></span>
<span id="cb22-123"><a href="#cb22-123" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (x <> y) = fmap f x <> fmap f y</span></span>
<span id="cb22-124"><a href="#cb22-124" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-125"><a href="#cb22-125" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-126"><a href="#cb22-126" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-127"><a href="#cb22-127" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-128"><a href="#cb22-128" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-129"><a href="#cb22-129" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap <span class="op"><.></span> wMap)</span>
<span id="cb22-130"><a href="#cb22-130" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-131"><a href="#cb22-131" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-132"><a href="#cb22-132" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-133"><a href="#cb22-133" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-134"><a href="#cb22-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-135"><a href="#cb22-135" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u <span class="op">.</span> v) <span class="op"><*></span> <span class="fu">pure</span> w</span>
<span id="cb22-136"><a href="#cb22-136" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-137"><a href="#cb22-137" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> pure x = pure (f x)</span></span>
<span id="cb22-138"><a href="#cb22-138" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-139"><a href="#cb22-139" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-140"><a href="#cb22-140" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-141"><a href="#cb22-141" aria-hidden="true" tabindex="-1"></a> (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-142"><a href="#cb22-142" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-143"><a href="#cb22-143" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap <span class="op"><.></span> wMap)</span>
<span id="cb22-144"><a href="#cb22-144" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-145"><a href="#cb22-145" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-146"><a href="#cb22-146" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-147"><a href="#cb22-147" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-148"><a href="#cb22-148" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-149"><a href="#cb22-149" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-150"><a href="#cb22-150" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-151"><a href="#cb22-151" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap (.) u <.> v <.> w = u <.> (v <.> w)</span></span>
<span id="cb22-152"><a href="#cb22-152" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-153"><a href="#cb22-153" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-154"><a href="#cb22-154" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-155"><a href="#cb22-155" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-156"><a href="#cb22-156" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-157"><a href="#cb22-157" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap <span class="op"><.></span> wMap)</span>
<span id="cb22-158"><a href="#cb22-158" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-159"><a href="#cb22-159" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-160"><a href="#cb22-160" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-161"><a href="#cb22-161" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-162"><a href="#cb22-162" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-163"><a href="#cb22-163" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-164"><a href="#cb22-164" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-165"><a href="#cb22-165" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (x <.> y) = fmap (f .) x <.> y</span></span>
<span id="cb22-166"><a href="#cb22-166" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-167"><a href="#cb22-167" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-168"><a href="#cb22-168" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-169"><a href="#cb22-169" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-170"><a href="#cb22-170" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-171"><a href="#cb22-171" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap <span class="op"><.></span> wMap)</span>
<span id="cb22-172"><a href="#cb22-172" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-173"><a href="#cb22-173" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-174"><a href="#cb22-174" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-175"><a href="#cb22-175" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-176"><a href="#cb22-176" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-177"><a href="#cb22-177" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-178"><a href="#cb22-178" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-179"><a href="#cb22-179" aria-hidden="true" tabindex="-1"></a><span class="co">-- x <.> fmap f y = fmap (. f) x <.> y</span></span>
<span id="cb22-180"><a href="#cb22-180" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-181"><a href="#cb22-181" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-182"><a href="#cb22-182" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-183"><a href="#cb22-183" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-184"><a href="#cb22-184" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-185"><a href="#cb22-185" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-186"><a href="#cb22-186" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-187"><a href="#cb22-187" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-188"><a href="#cb22-188" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-189"><a href="#cb22-189" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-190"><a href="#cb22-190" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-191"><a href="#cb22-191" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-192"><a href="#cb22-192" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-193"><a href="#cb22-193" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (x <.> y) = fmap (f .) x <.> y</span></span>
<span id="cb22-194"><a href="#cb22-194" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-195"><a href="#cb22-195" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-196"><a href="#cb22-196" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-197"><a href="#cb22-197" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-198"><a href="#cb22-198" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-199"><a href="#cb22-199" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-200"><a href="#cb22-200" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-201"><a href="#cb22-201" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> ((<span class="op">$</span> w) <span class="op">.</span>) (<span class="fu">fmap</span> (<span class="op">.</span>) uMap) <span class="op"><.></span> vMap)</span>
<span id="cb22-202"><a href="#cb22-202" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-203"><a href="#cb22-203" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-204"><a href="#cb22-204" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-205"><a href="#cb22-205" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-206"><a href="#cb22-206" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-207"><a href="#cb22-207" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) x</span></span>
<span id="cb22-208"><a href="#cb22-208" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-209"><a href="#cb22-209" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-210"><a href="#cb22-210" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-211"><a href="#cb22-211" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-212"><a href="#cb22-212" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-213"><a href="#cb22-213" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-214"><a href="#cb22-214" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-215"><a href="#cb22-215" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (((<span class="op">$</span> w) <span class="op">.</span>) <span class="op">.</span> (<span class="op">.</span>)) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-216"><a href="#cb22-216" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-217"><a href="#cb22-217" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-218"><a href="#cb22-218" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-219"><a href="#cb22-219" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-220"><a href="#cb22-220" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-221"><a href="#cb22-221" aria-hidden="true" tabindex="-1"></a><span class="co">-- ((($ w) .) . (.))</span></span>
<span id="cb22-222"><a href="#cb22-222" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u -> (($ w) .) ((.) u)</span></span>
<span id="cb22-223"><a href="#cb22-223" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u -> (($ w) .) (u .)</span></span>
<span id="cb22-224"><a href="#cb22-224" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u -> ($ w) . (u .)</span></span>
<span id="cb22-225"><a href="#cb22-225" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u v -> ($ w) ((u .) v)</span></span>
<span id="cb22-226"><a href="#cb22-226" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u v -> ($ w) (u . v)</span></span>
<span id="cb22-227"><a href="#cb22-227" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u v -> (u . v) w</span></span>
<span id="cb22-228"><a href="#cb22-228" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u v -> u (v w)</span></span>
<span id="cb22-229"><a href="#cb22-229" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u v -> u (($ w) v)</span></span>
<span id="cb22-230"><a href="#cb22-230" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \u -> u . ($ w)</span></span>
<span id="cb22-231"><a href="#cb22-231" aria-hidden="true" tabindex="-1"></a><span class="co">-- = (. ($ w))</span></span>
<span id="cb22-232"><a href="#cb22-232" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-233"><a href="#cb22-233" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-234"><a href="#cb22-234" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-235"><a href="#cb22-235" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-236"><a href="#cb22-236" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-237"><a href="#cb22-237" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-238"><a href="#cb22-238" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-239"><a href="#cb22-239" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (<span class="fu">fmap</span> (<span class="op">.</span> (<span class="op">$</span> w)) uMap <span class="op"><.></span> vMap)</span>
<span id="cb22-240"><a href="#cb22-240" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-241"><a href="#cb22-241" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-242"><a href="#cb22-242" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-243"><a href="#cb22-243" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-244"><a href="#cb22-244" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-245"><a href="#cb22-245" aria-hidden="true" tabindex="-1"></a><span class="co">-- x <.> (f <$> y) = (. f) <$> x <.> y</span></span>
<span id="cb22-246"><a href="#cb22-246" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-247"><a href="#cb22-247" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-248"><a href="#cb22-248" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-249"><a href="#cb22-249" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-250"><a href="#cb22-250" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-251"><a href="#cb22-251" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-252"><a href="#cb22-252" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap</span>
<span id="cb22-253"><a href="#cb22-253" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-254"><a href="#cb22-254" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (u <span class="op">.</span>) vMap)</span>
<span id="cb22-255"><a href="#cb22-255" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) (<span class="fu">fmap</span> (<span class="op">.</span> v) uMap)</span>
<span id="cb22-256"><a href="#cb22-256" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-257"><a href="#cb22-257" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-258"><a href="#cb22-258" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-259"><a href="#cb22-259" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) w</span></span>
<span id="cb22-260"><a href="#cb22-260" aria-hidden="true" tabindex="-1"></a><span class="co">-- (f . g) = \x -> f (g x)</span></span>
<span id="cb22-261"><a href="#cb22-261" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-262"><a href="#cb22-262" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-263"><a href="#cb22-263" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-264"><a href="#cb22-264" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-265"><a href="#cb22-265" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-266"><a href="#cb22-266" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-267"><a href="#cb22-267" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\w <span class="ot">-></span> u (v w)) wMap</span>
<span id="cb22-268"><a href="#cb22-268" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-269"><a href="#cb22-269" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\v <span class="ot">-></span> u (v w)) vMap</span>
<span id="cb22-270"><a href="#cb22-270" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-271"><a href="#cb22-271" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-272"><a href="#cb22-272" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-273"><a href="#cb22-273" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-274"><a href="#cb22-274" aria-hidden="true" tabindex="-1"></a><span class="co">-- `fmap (\w -> u (v w)) wMap <> (uMap <.> fmap ($ w) vMap)` commutes because</span></span>
<span id="cb22-275"><a href="#cb22-275" aria-hidden="true" tabindex="-1"></a><span class="co">-- the colliding keys are shadowed by `(uMap <.> (vMap <.> wMap))`</span></span>
<span id="cb22-276"><a href="#cb22-276" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-277"><a href="#cb22-277" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-278"><a href="#cb22-278" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-279"><a href="#cb22-279" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-280"><a href="#cb22-280" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-281"><a href="#cb22-281" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-282"><a href="#cb22-282" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-283"><a href="#cb22-283" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\w <span class="ot">-></span> u (v w)) wMap</span>
<span id="cb22-284"><a href="#cb22-284" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\v <span class="ot">-></span> u (v w)) vMap</span>
<span id="cb22-285"><a href="#cb22-285" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-286"><a href="#cb22-286" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-287"><a href="#cb22-287" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-288"><a href="#cb22-288" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-289"><a href="#cb22-289" aria-hidden="true" tabindex="-1"></a><span class="co">-- `fmap u (vMap <.> wMap) <> (uMap <.> fmap v wMap)` commutes because the</span></span>
<span id="cb22-290"><a href="#cb22-290" aria-hidden="true" tabindex="-1"></a><span class="co">-- colliding keys are sahdowed by `(uMap <.> (vMap <.> wMap))`</span></span>
<span id="cb22-291"><a href="#cb22-291" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-292"><a href="#cb22-292" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-293"><a href="#cb22-293" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-294"><a href="#cb22-294" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-295"><a href="#cb22-295" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-296"><a href="#cb22-296" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-297"><a href="#cb22-297" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-298"><a href="#cb22-298" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\w <span class="ot">-></span> u (v w)) wMap</span>
<span id="cb22-299"><a href="#cb22-299" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\v <span class="ot">-></span> u (v w)) vMap</span>
<span id="cb22-300"><a href="#cb22-300" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-301"><a href="#cb22-301" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-302"><a href="#cb22-302" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-303"><a href="#cb22-303" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-304"><a href="#cb22-304" aria-hidden="true" tabindex="-1"></a><span class="co">-- `fmap u (vMap <.> wMap) <> (uMap <.> fmap ($ w) vMap)` commutes because the</span></span>
<span id="cb22-305"><a href="#cb22-305" aria-hidden="true" tabindex="-1"></a><span class="co">-- colliding keys are sahdowed by `(uMap <.> (vMap <.> wMap))`</span></span>
<span id="cb22-306"><a href="#cb22-306" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-307"><a href="#cb22-307" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-308"><a href="#cb22-308" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-309"><a href="#cb22-309" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-310"><a href="#cb22-310" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> v wMap)</span>
<span id="cb22-311"><a href="#cb22-311" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-312"><a href="#cb22-312" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-313"><a href="#cb22-313" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\w <span class="ot">-></span> u (v w)) wMap</span>
<span id="cb22-314"><a href="#cb22-314" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\v <span class="ot">-></span> u (v w)) vMap</span>
<span id="cb22-315"><a href="#cb22-315" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-316"><a href="#cb22-316" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-317"><a href="#cb22-317" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-318"><a href="#cb22-318" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-319"><a href="#cb22-319" aria-hidden="true" tabindex="-1"></a><span class="co">-- \w -> u (v w) = u . v</span></span>
<span id="cb22-320"><a href="#cb22-320" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-321"><a href="#cb22-321" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-322"><a href="#cb22-322" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-323"><a href="#cb22-323" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-324"><a href="#cb22-324" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-325"><a href="#cb22-325" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-326"><a href="#cb22-326" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-327"><a href="#cb22-327" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> v) wMap)</span>
<span id="cb22-328"><a href="#cb22-328" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\v <span class="ot">-></span> u (v w)) vMap</span>
<span id="cb22-329"><a href="#cb22-329" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-330"><a href="#cb22-330" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-331"><a href="#cb22-331" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-332"><a href="#cb22-332" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-333"><a href="#cb22-333" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) x, in reverse</span></span>
<span id="cb22-334"><a href="#cb22-334" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-335"><a href="#cb22-335" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-336"><a href="#cb22-336" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-337"><a href="#cb22-337" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-338"><a href="#cb22-338" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-339"><a href="#cb22-339" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-340"><a href="#cb22-340" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-341"><a href="#cb22-341" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-342"><a href="#cb22-342" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\v <span class="ot">-></span> u (v w)) vMap</span>
<span id="cb22-343"><a href="#cb22-343" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-344"><a href="#cb22-344" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-345"><a href="#cb22-345" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-346"><a href="#cb22-346" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-347"><a href="#cb22-347" aria-hidden="true" tabindex="-1"></a><span class="co">-- \v -> u (v w)</span></span>
<span id="cb22-348"><a href="#cb22-348" aria-hidden="true" tabindex="-1"></a><span class="co">-- = \v -> u (($ w) v)</span></span>
<span id="cb22-349"><a href="#cb22-349" aria-hidden="true" tabindex="-1"></a><span class="co">-- = u . ($ w)</span></span>
<span id="cb22-350"><a href="#cb22-350" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-351"><a href="#cb22-351" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-352"><a href="#cb22-352" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-353"><a href="#cb22-353" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-354"><a href="#cb22-354" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-355"><a href="#cb22-355" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-356"><a href="#cb22-356" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-357"><a href="#cb22-357" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-358"><a href="#cb22-358" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (u <span class="op">.</span> (<span class="op">$</span> w)) vMap</span>
<span id="cb22-359"><a href="#cb22-359" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-360"><a href="#cb22-360" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-361"><a href="#cb22-361" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-362"><a href="#cb22-362" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-363"><a href="#cb22-363" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) x, in reverse</span></span>
<span id="cb22-364"><a href="#cb22-364" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-365"><a href="#cb22-365" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-366"><a href="#cb22-366" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-367"><a href="#cb22-367" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-368"><a href="#cb22-368" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-369"><a href="#cb22-369" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-370"><a href="#cb22-370" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-371"><a href="#cb22-371" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-372"><a href="#cb22-372" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (<span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-373"><a href="#cb22-373" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (\u <span class="ot">-></span> u (v w)) uMap</span>
<span id="cb22-374"><a href="#cb22-374" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-375"><a href="#cb22-375" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-376"><a href="#cb22-376" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-377"><a href="#cb22-377" aria-hidden="true" tabindex="-1"></a><span class="co">-- \f -> f x = ($ x)</span></span>
<span id="cb22-378"><a href="#cb22-378" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-379"><a href="#cb22-379" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-380"><a href="#cb22-380" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span></span>
<span id="cb22-381"><a href="#cb22-381" aria-hidden="true" tabindex="-1"></a> (uMap <span class="op"><.></span> (vMap <span class="op"><.></span> wMap))</span>
<span id="cb22-382"><a href="#cb22-382" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-383"><a href="#cb22-383" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> (uMap <span class="op"><.></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-384"><a href="#cb22-384" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (vMap <span class="op"><.></span> wMap)</span>
<span id="cb22-385"><a href="#cb22-385" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (<span class="fu">fmap</span> v wMap)</span>
<span id="cb22-386"><a href="#cb22-386" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> u (<span class="fu">fmap</span> (<span class="op">$</span> w) vMap)</span>
<span id="cb22-387"><a href="#cb22-387" aria-hidden="true" tabindex="-1"></a> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> (v w)) uMap</span>
<span id="cb22-388"><a href="#cb22-388" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-389"><a href="#cb22-389" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-390"><a href="#cb22-390" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-391"><a href="#cb22-391" aria-hidden="true" tabindex="-1"></a><span class="co">-- f <.> (x <> y) = (f <.> x) <> (f <.> y), in reverse</span></span>
<span id="cb22-392"><a href="#cb22-392" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (x <> y) = fmap f x <> fmap f y, in reverse</span></span>
<span id="cb22-393"><a href="#cb22-393" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-394"><a href="#cb22-394" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-395"><a href="#cb22-395" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span> (uMap <span class="op"><.></span> vwMap) <span class="op"><></span> <span class="fu">fmap</span> u vwMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> (v w)) uMap</span>
<span id="cb22-396"><a href="#cb22-396" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-397"><a href="#cb22-397" aria-hidden="true" tabindex="-1"></a> vwMap <span class="ot">=</span> (vMap <span class="op"><.></span> wMap) <span class="op"><></span> <span class="fu">fmap</span> v wMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap</span>
<span id="cb22-398"><a href="#cb22-398" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-399"><a href="#cb22-399" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> (u (v w))</span>
<span id="cb22-400"><a href="#cb22-400" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-401"><a href="#cb22-401" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> pure x = pure (f x), in reverse</span></span>
<span id="cb22-402"><a href="#cb22-402" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> cuvwMap cuvwDefault</span>
<span id="cb22-403"><a href="#cb22-403" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-404"><a href="#cb22-404" aria-hidden="true" tabindex="-1"></a> cuvwMap <span class="ot">=</span> (uMap <span class="op"><.></span> vwMap) <span class="op"><></span> <span class="fu">fmap</span> u vwMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> (v w)) uMap</span>
<span id="cb22-405"><a href="#cb22-405" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-406"><a href="#cb22-406" aria-hidden="true" tabindex="-1"></a> vwMap <span class="ot">=</span> (vMap <span class="op"><.></span> wMap) <span class="op"><></span> <span class="fu">fmap</span> v wMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap</span>
<span id="cb22-407"><a href="#cb22-407" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-408"><a href="#cb22-408" aria-hidden="true" tabindex="-1"></a> cuvwDefault <span class="ot">=</span> <span class="fu">pure</span> u <span class="op"><*></span> <span class="fu">pure</span> (v w)</span>
<span id="cb22-409"><a href="#cb22-409" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-410"><a href="#cb22-410" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>), in reverse</span></span>
<span id="cb22-411"><a href="#cb22-411" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> ( <span class="dt">Defaultable</span> uMap (<span class="fu">pure</span> u)</span>
<span id="cb22-412"><a href="#cb22-412" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> (<span class="dt">Defaultable</span> vwMap vwDefault</span>
<span id="cb22-413"><a href="#cb22-413" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-414"><a href="#cb22-414" aria-hidden="true" tabindex="-1"></a> vwMap <span class="ot">=</span> (vMap <span class="op"><.></span> wMap) <span class="op"><></span> <span class="fu">fmap</span> v wMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap</span>
<span id="cb22-415"><a href="#cb22-415" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-416"><a href="#cb22-416" aria-hidden="true" tabindex="-1"></a> vwDefault <span class="ot">=</span> <span class="fu">pure</span> (v w)</span>
<span id="cb22-417"><a href="#cb22-417" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-418"><a href="#cb22-418" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-419"><a href="#cb22-419" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-420"><a href="#cb22-420" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> pure x = pure (f x), in reverse</span></span>
<span id="cb22-421"><a href="#cb22-421" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> ( <span class="dt">Defaultable</span> uMap (<span class="fu">pure</span> u)</span>
<span id="cb22-422"><a href="#cb22-422" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> (<span class="dt">Defaultable</span> vwMap vwDefault</span>
<span id="cb22-423"><a href="#cb22-423" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb22-424"><a href="#cb22-424" aria-hidden="true" tabindex="-1"></a> vwMap <span class="ot">=</span> (vMap <span class="op"><.></span> wMap) <span class="op"><></span> <span class="fu">fmap</span> v wMap <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> w) vMap </span>
<span id="cb22-425"><a href="#cb22-425" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-426"><a href="#cb22-426" aria-hidden="true" tabindex="-1"></a> vwDefault <span class="ot">=</span> <span class="fu">pure</span> v <span class="op"><*></span> <span class="fu">pure</span> w</span>
<span id="cb22-427"><a href="#cb22-427" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-428"><a href="#cb22-428" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-429"><a href="#cb22-429" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-430"><a href="#cb22-430" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>), in reverse</span></span>
<span id="cb22-431"><a href="#cb22-431" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> ( <span class="dt">Defaultable</span> uMap (<span class="fu">pure</span> u)</span>
<span id="cb22-432"><a href="#cb22-432" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Defaultable</span> vMap (<span class="fu">pure</span> v)</span>
<span id="cb22-433"><a href="#cb22-433" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Defaultable</span> wMap (<span class="fu">pure</span> w)</span>
<span id="cb22-434"><a href="#cb22-434" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-435"><a href="#cb22-435" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb22-436"><a href="#cb22-436" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-437"><a href="#cb22-437" aria-hidden="true" tabindex="-1"></a><span class="co">-- Contract:</span></span>
<span id="cb22-438"><a href="#cb22-438" aria-hidden="true" tabindex="-1"></a><span class="co">-- u = Defaultable uMap uDefault</span></span>
<span id="cb22-439"><a href="#cb22-439" aria-hidden="true" tabindex="-1"></a><span class="co">-- v = Defaultable vMap vDefault</span></span>
<span id="cb22-440"><a href="#cb22-440" aria-hidden="true" tabindex="-1"></a><span class="co">-- w = Defaultable wMap wDefault</span></span>
<span id="cb22-441"><a href="#cb22-441" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> u <span class="op"><*></span> (v <span class="op"><*></span> w)</span></code></pre></div>
<h5 id="proof-of-homomorphism-law">Proof of homomorphism law</h5>
<div class="sourceCode" id="cb23"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x</span>
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure v = Defaultable mempty (pure v)</span></span>
<span id="cb23-4"><a href="#cb23-4" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> f) <span class="op"><*></span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> x)</span>
<span id="cb23-5"><a href="#cb23-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-6"><a href="#cb23-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>)</span></span>
<span id="cb23-7"><a href="#cb23-7" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb23-8"><a href="#cb23-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb23-9"><a href="#cb23-9" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> (<span class="fu">mempty</span> <span class="op"><.></span> <span class="fu">mempty</span>) <span class="op"><></span> <span class="fu">fmap</span> f <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> x) <span class="fu">mempty</span></span>
<span id="cb23-10"><a href="#cb23-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-11"><a href="#cb23-11" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x</span>
<span id="cb23-12"><a href="#cb23-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-13"><a href="#cb23-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f mempty = mempty</span></span>
<span id="cb23-14"><a href="#cb23-14" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb23-15"><a href="#cb23-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb23-16"><a href="#cb23-16" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> (<span class="fu">mempty</span> <span class="op"><.></span> <span class="fu">mempty</span>) <span class="op"><></span> <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">mempty</span></span>
<span id="cb23-17"><a href="#cb23-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-18"><a href="#cb23-18" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x</span>
<span id="cb23-19"><a href="#cb23-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-20"><a href="#cb23-20" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <.> x = mempty</span></span>
<span id="cb23-21"><a href="#cb23-21" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb23-22"><a href="#cb23-22" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb23-23"><a href="#cb23-23" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">mempty</span> <span class="op"><></span> <span class="fu">mempty</span></span>
<span id="cb23-24"><a href="#cb23-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-25"><a href="#cb23-25" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x</span>
<span id="cb23-26"><a href="#cb23-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-27"><a href="#cb23-27" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <> x = x</span></span>
<span id="cb23-28"><a href="#cb23-28" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb23-29"><a href="#cb23-29" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb23-30"><a href="#cb23-30" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb23-31"><a href="#cb23-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-32"><a href="#cb23-32" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x</span>
<span id="cb23-33"><a href="#cb23-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-34"><a href="#cb23-34" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> pure x = pure (f x)</span></span>
<span id="cb23-35"><a href="#cb23-35" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> fxMap fxDefault</span>
<span id="cb23-36"><a href="#cb23-36" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb23-37"><a href="#cb23-37" aria-hidden="true" tabindex="-1"></a> fxMap <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb23-38"><a href="#cb23-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-39"><a href="#cb23-39" aria-hidden="true" tabindex="-1"></a> fxDefault <span class="ot">=</span> <span class="fu">pure</span> (f x)</span>
<span id="cb23-40"><a href="#cb23-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-41"><a href="#cb23-41" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify</span></span>
<span id="cb23-42"><a href="#cb23-42" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> (f x))</span>
<span id="cb23-43"><a href="#cb23-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-44"><a href="#cb23-44" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure v = Defaultable mempty (pure v), in reverse</span></span>
<span id="cb23-45"><a href="#cb23-45" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (f x)</span></code></pre></div>
<h5 id="proof-of-interchange-law">Proof of interchange law</h5>
<div class="sourceCode" id="cb24"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a>u <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure v = Defaultable mempty (pure v)</span></span>
<span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> u <span class="op"><*></span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> y)</span>
<span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- Expand: u = Defaultable uMap uDefault</span></span>
<span id="cb24-7"><a href="#cb24-7" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uMap uDefault <span class="op"><*></span> <span class="dt">Defaultable</span> <span class="fu">mempty</span> (<span class="fu">pure</span> y)</span>
<span id="cb24-8"><a href="#cb24-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-9"><a href="#cb24-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of (<*>)</span></span>
<span id="cb24-10"><a href="#cb24-10" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-11"><a href="#cb24-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-12"><a href="#cb24-12" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> (uMap <span class="op"><.></span> <span class="fu">mempty</span>) <span class="op"><></span> uFallback <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-13"><a href="#cb24-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-14"><a href="#cb24-14" aria-hidden="true" tabindex="-1"></a> uFallback <span class="ot">=</span></span>
<span id="cb24-15"><a href="#cb24-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> uDefault <span class="kw">of</span></span>
<span id="cb24-16"><a href="#cb24-16" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb24-17"><a href="#cb24-17" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> u <span class="ot">-></span> <span class="fu">fmap</span> y <span class="fu">mempty</span></span>
<span id="cb24-18"><a href="#cb24-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-19"><a href="#cb24-19" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> uDefault <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb24-20"><a href="#cb24-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-21"><a href="#cb24-21" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f mempty = mempty</span></span>
<span id="cb24-22"><a href="#cb24-22" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-23"><a href="#cb24-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-24"><a href="#cb24-24" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> (uMap <span class="op"><.></span> <span class="fu">mempty</span>) <span class="op"><></span> uFallback <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-25"><a href="#cb24-25" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-26"><a href="#cb24-26" aria-hidden="true" tabindex="-1"></a> uFallback <span class="ot">=</span></span>
<span id="cb24-27"><a href="#cb24-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> uDefault <span class="kw">of</span></span>
<span id="cb24-28"><a href="#cb24-28" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb24-29"><a href="#cb24-29" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> u <span class="ot">-></span> <span class="fu">mempty</span></span>
<span id="cb24-30"><a href="#cb24-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-31"><a href="#cb24-31" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> uDefault <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb24-32"><a href="#cb24-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-33"><a href="#cb24-33" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify `case uDefault of …`</span></span>
<span id="cb24-34"><a href="#cb24-34" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-35"><a href="#cb24-35" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-36"><a href="#cb24-36" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> (uMap <span class="op"><.></span> <span class="fu">mempty</span>) <span class="op"><></span> uFallback <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-37"><a href="#cb24-37" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-38"><a href="#cb24-38" aria-hidden="true" tabindex="-1"></a> uFallback <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb24-39"><a href="#cb24-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-40"><a href="#cb24-40" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> uDefault <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb24-41"><a href="#cb24-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-42"><a href="#cb24-42" aria-hidden="true" tabindex="-1"></a><span class="co">-- f <.> mempty = mempty</span></span>
<span id="cb24-43"><a href="#cb24-43" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-44"><a href="#cb24-44" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-45"><a href="#cb24-45" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> <span class="fu">mempty</span> <span class="op"><></span> uFallback <span class="op"><></span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-46"><a href="#cb24-46" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-47"><a href="#cb24-47" aria-hidden="true" tabindex="-1"></a> uFallback <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb24-48"><a href="#cb24-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-49"><a href="#cb24-49" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> uDefault <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb24-50"><a href="#cb24-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-51"><a href="#cb24-51" aria-hidden="true" tabindex="-1"></a><span class="co">-- mempty <> x = x</span></span>
<span id="cb24-52"><a href="#cb24-52" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-53"><a href="#cb24-53" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-54"><a href="#cb24-54" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-55"><a href="#cb24-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-56"><a href="#cb24-56" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> uDefault <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb24-57"><a href="#cb24-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-58"><a href="#cb24-58" aria-hidden="true" tabindex="-1"></a><span class="co">-- u <*> pure y = pure ($ y) <*> u</span></span>
<span id="cb24-59"><a href="#cb24-59" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-60"><a href="#cb24-60" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-61"><a href="#cb24-61" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-62"><a href="#cb24-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-63"><a href="#cb24-63" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> <span class="fu">pure</span> (<span class="op">$</span> y) <span class="op"><*></span> uDefault</span>
<span id="cb24-64"><a href="#cb24-64" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-65"><a href="#cb24-65" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> x = fmap f x</span></span>
<span id="cb24-66"><a href="#cb24-66" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Defaultable</span> uyMap uyDefault</span>
<span id="cb24-67"><a href="#cb24-67" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb24-68"><a href="#cb24-68" aria-hidden="true" tabindex="-1"></a> uyMap <span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">$</span> y) uMap</span>
<span id="cb24-69"><a href="#cb24-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-70"><a href="#cb24-70" aria-hidden="true" tabindex="-1"></a> uyDefault <span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">$</span> y) uDefault</span>
<span id="cb24-71"><a href="#cb24-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-72"><a href="#cb24-72" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `fmap`, in reverse</span></span>
<span id="cb24-73"><a href="#cb24-73" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">$</span> y) (<span class="dt">Defaultable</span> uMap uDefault)</span>
<span id="cb24-74"><a href="#cb24-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-75"><a href="#cb24-75" aria-hidden="true" tabindex="-1"></a><span class="co">-- Contract: u = Defaultable uMap uDefault</span></span>
<span id="cb24-76"><a href="#cb24-76" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">fmap</span> (<span class="op">$</span> y) u</span>
<span id="cb24-77"><a href="#cb24-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb24-78"><a href="#cb24-78" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure f <*> x = fmap f x, in reverse</span></span>
<span id="cb24-79"><a href="#cb24-79" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (<span class="op">$</span> y) <span class="op"><*></span> u</span></code></pre></div>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-16592492161937448702022-06-03T06:12:00.008-07:002022-06-10T15:04:50.943-07:00The appeal of bidirectional type-checking<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="The appeal of bidirectional type-checking" />
<meta name="twitter:description" content="A post explaining why bidirectional type-checking is popular within the programming language theory community." />
<title>The appeal of bidirectional type-checking</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>In this post I hope to explain why <a href="https://www.cl.cam.ac.uk/~nk480/bidir.pdf">bidirectional type-checking</a> has a lot of cultural cachet within the programming language theory community. To be clear, I’m an amateur and I have no formal background in computer science or type theory. Nonetheless, I believe I’ve learned enough and compared notes with others to motivate bidirectional type-checking.</p>
<h4 id="subtyping">Subtyping</h4>
<p>The fundamental problem that bidirectional type-checking solves well is <strong>subtyping</strong>. I will not explain why in this post, so for now take it as an article of faith that bidirectional type-checking provides a simple and consistent framework for supporting subtyping within a programming language’s type system.</p>
<p>Bidirectional type-checking does other things well, too (such as inferring the types of inputs from outputs), but subtyping is the thing that bidirectional type-checking does uniquely well. For example, you can infer inputs from outputs using unification instead of bidirectional type-checking, which is why I don't motivate bidirectional type-checking in terms of doing inference "in reverse".</p>
<p>By “subtyping”, I mean that a type <code>A</code> is a subtype of type <code>B</code> if any expression of type <code>A</code> is also a valid expression of type <code>B</code>.</p>
<p>For example, we could create a language that provides two numeric types:</p>
<ul>
<li><p><code>Natural</code> - The type of non-negative integers (ℕ)</p></li>
<li><p><code>Integer</code> - The type of all integers (ℤ)</p></li>
</ul>
<p>Furthermore, we could define <code>Natural</code> to be a subtype of <code>Integer</code> (i.e. ℕ ⊆ ℤ). In other words, if a scalar literal like <code>4</code> were a valid <code>Natural</code> number then <code>4</code> would also be a valid <code>Integer</code>, too. That would permit us to write something like this <a href="https://github.com/Gabriella439/grace">Fall-from-Grace</a> code:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> x <span class="ot">=</span> <span class="dv">4</span> <span class="op">:</span> <span class="dt">Natural</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> [ x, <span class="op">-</span><span class="dv">5</span> ] <span class="op">:</span> <span class="dt">List</span> <span class="dt">Integer</span></span></code></pre></div>
<p>… and the type-checker would not complain that we put a <code>Natural</code> number inside a <code>List</code> of <code>Integer</code>s, because a <code>Natural</code> number is a subtype of an <code>Integer</code>.</p>
<h4 id="why-subtyping-matters">Why subtyping matters</h4>
<p>Now, automatic numeric coercions like that are convenient but in the grand scheme of things they are not a big deal from a language implementer’s point of view. The real appeal of subtyping is that subtyping appears in more places than you’d expect.</p>
<p>I’m not even talking about object-oriented subtyping like “The <code>Dog</code> class is a subtype of the <code>Animal</code> class”. Subtyping occurs quite frequently in even non-OOP languages, in the form of universal quantification (a.k.a. “polymorphism” or “generics”).</p>
<p>For example, we can define a polymorphic identity function, but then use the function as if it had a narrower (more specialized) type:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="fu">id</span> <span class="op">:</span> <span class="kw">forall</span> (a <span class="op">:</span> <span class="dt">Type</span>) <span class="op">.</span> a <span class="ot">-></span> a</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \x <span class="ot">-></span> x</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="fu">id</span> <span class="op">:</span> <span class="dt">Text</span> <span class="ot">-></span> <span class="dt">Text</span></span></code></pre></div>
<p>… and that works because <code>forall (a : Type) . a -> a</code> is a subtype of <code>Text -> Text</code>.</p>
<p>Yes, you read that right; I didn’t get the subtyping direction backwards. A polymorphic type is a subtype of a more specialized type.</p>
<p>In fact, <code>forall (a : Type) . a -> a</code> is a subtype of multiple types, such as:</p>
<ul>
<li><code>Integer -> Integer</code></li>
<li><code>List Natural -> List Natural</code></li>
<li><code>{ x: Bool } -> { x: Bool }</code></li>
<li>…</li>
</ul>
<p>… and so on. This might be a bit counter-intuitive if you come from an OOP background where usually each type is a subtype of at most one other (explicitly declared) supertype.</p>
<p>This type specialization is implicit, meaning that we don’t need the type annotation to use <code>id</code> on a value of type <code>Text</code>. Instead, the specialization happens automatically, so we can use <code>id</code> on any value without annotations:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="fu">id</span> <span class="op">:</span> <span class="kw">forall</span> (a <span class="op">:</span> <span class="dt">Type</span>) <span class="op">.</span> a <span class="ot">-></span> a</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \x <span class="ot">-></span> x</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="fu">id</span> <span class="st">"ABC"</span></span></code></pre></div>
<p>If our language didn’t support subtyping then we’d need to explicitly abstract over and apply type arguments. For example, this is how that same <code>id</code> example would work in <a href="https://dhall-lang.org">Dhall</a> (which does not support any form of subtyping):</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="fu">id</span> <span class="op">:</span> <span class="kw">forall</span> (a <span class="op">:</span> <span class="dt">Type</span>) <span class="ot">-></span> a <span class="ot">-></span> a</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> <span class="ot">=</span> \(a <span class="op">:</span> <span class="dt">Type</span>) <span class="ot">-></span> \(x <span class="op">:</span> a) <span class="ot">-></span> x</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ↑ Explicit type abstraction</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="fu">id</span> <span class="dt">Text</span> <span class="st">"ABC"</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ↑ Explicit type application</span></span></code></pre></div>
<p>Typically I refer to a Dhall-style type system as “explicit type abstraction and type application”. Vice versa, I refer to a Grace-style language as “implicit type abstraction and type application”.</p>
<p>The real reason why subtyping matters is because you need to support subtyping in order to implement a language with “implicit type abstraction/application”. In other words, you need some way to automatically produce and consume polymorphic types without explicit types or type annotations from the end user and that is essentially a form of subtyping.</p>
<h4 id="bidirectional-type-checking-vs-hindley-milner">Bidirectional type-checking vs unification</h4>
<p>Bidirectional type-checking is not the only way to implement a language with implicit type abstraction/application. For example, <a href="https://en.wikipedia.org/wiki/Hindley–Milner_type_system">Hindley-Milner type inference</a> is a type system that is based on unification instead of bidirectional type-checking type system and Hindley-Milner inference still supports implicit type abstraction/application.</p>
<p>The reason why Hindley Milner type inference works, though, is by exploiting a narrow set of requirements that do not generalize well to more sophisticated type systems. Specifically:</p>
<ul>
<li><p>Hindley Milner type inference only permits “top-level polymorphism”</p>
<p>In other words, Hindley Milner type inference only permits universal quantification at the top-level of a program or at the top-level of a <code>let</code>-bound expression</p></li>
<li><p>Hindley Milner type inference does not support any other form of subtyping</p></li>
</ul>
<p>To expand on the latter point, language implementers often want to add other forms of subtyping to our languages beyond implicit type abstraction/application, such as:</p>
<ul>
<li>Implicit numeric coercions</li>
<li>Object-oriented inheritance</li>
<li><a href="https://en.wikipedia.org/wiki/Existential_quantification">Existential quantification</a> (the dual of universal quantification)</li>
<li><a href="https://en.wikipedia.org/wiki/Refinement_type">Refinement types</a></li>
</ul>
<p>… and if you try to extend Hindley Milner type inference to add any other language feature that requires subtyping then you run into issues pretty quickly. This is because Hindley Milner exploits a cute hack that does not generalize well.</p>
<p>In contrast, bidirectional type-checking is not just a method for handling implicit type abstraction/application. Rather, bidirectional type-checking is a framework for introducing any form of subtyping, of which implicit type abstraction/application is just a special case.</p>
<h4 id="conclusion">Conclusion</h4>
<p>If you’d like to learn more about bidirectional type-checking then I recommend reading:</p>
<ul>
<li><p><a href="https://www.cl.cam.ac.uk/~nk480/bidir.pdf">Complete and Easy Bidirectional Typechecking for Higher-Rank Polymorphism</a></p>
<p>This paper provides an all-in-one presentation of how to implement a bidirectional type-checking system</p></li>
<li><p><a href="https://www.cl.cam.ac.uk/~nk480/bidir-survey.pdf">Bidirectional Typing</a></p>
<p>This explains the general principles behind bidirectional type-checking so that you can better understand how to extend the type system with new features</p></li>
</ul>
<p>As I mentioned earlier, bidirectional type-checking has a lot of cultural cachet within the programming language theory community, so even if you don’t plan on using it, understanding it will help you speak the same language as many others within the community.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com2tag:blogger.com,1999:blog-1777990983847811806.post-73525090061089641062022-05-31T09:13:00.004-07:002022-05-31T09:26:13.366-07:00Generate web forms from pure functions<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Generate web forms from pure functions</title>
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="Generate web forms from pure functions" />
<meta name="twitter:description" content="Announce post for the Grace browser, which translates functional code to interactive HTML" />
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This is an announcement post for my “Grace browser” project, which you can find here:</p>
<ul>
<li><a href="https://trygrace.dev"><code>trygrace.dev</code></a></li>
</ul>
<p>This project is a web page which can dynamically convert a wide variety of functional programming expressions to their equivalent HTML. This conversion can even auto-generate interactive web forms from functions, which means that people without web programming knowledge can use the Grace browser to create and share simple and interactive web pages.</p>
<h4 id="demo">Demo</h4>
<p>You can quickly get the gist of this project by visiting the following page:</p>
<ul>
<li><a href="https://trygrace.dev/?expression=%255Cinput%2520-%253E%250A%2520%2520%2520%2520%257B%2520%2522x%2520or%2520y%2522%2520%253A%2520input.x%2520%257C%257C%2520input.y%250A%2520%2520%2520%2520%252C%2520%2522x%2520and%2520y%2522%253A%2520input.x%2520%2526%2526%2520input.y%250A%2520%2520%2520%2520%257D">Grace browser - Boolean logic example</a></li>
</ul>
<p>The Grace browser begins with a code editor where you can input a purely functional expression and the above link uses the following example code:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>\input <span class="ot">-></span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> { <span class="st">"x or y"</span> <span class="op">:</span> input<span class="op">.</span>x <span class="op">||</span> input<span class="op">.</span>y</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> , <span class="st">"x and y"</span><span class="op">:</span> input<span class="op">.</span>x <span class="op">&&</span> input<span class="op">.</span>y</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>The interpreter then infers the type of the input expression, which for the above example is:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>{ <span class="st">"x"</span><span class="op">:</span> <span class="dt">Bool</span>, <span class="st">"y"</span><span class="op">:</span> <span class="dt">Bool</span> } <span class="ot">-></span> { <span class="st">"x or y"</span><span class="op">:</span> <span class="dt">Bool</span>, <span class="st">"x and y"</span><span class="op">:</span> <span class="dt">Bool</span> }</span></code></pre></div>
<p>… and you can read that type as saying:</p>
<blockquote>
<p>This is a function whose input is a record and whose output is also a record. The input record has two boolean-valued fields named “x” and “y” and the output record has two boolean-valued fields named “x or y” and “x and y”.</p>
</blockquote>
<p>Here is the novel bit: the interpreter then <strong>generates a web interface appropriate for that type</strong>. In this case, the equivalent web interface is a form with two inputs:</p>
<ul>
<li>a checkbox labeled “x”</li>
<li>a checkbox labeled “y”</li>
</ul>
<p>… and the form also produces two outputs:</p>
<ul>
<li>a read-only checkbox labeled “x or y”</li>
<li>a read-only checkbox labeled “x and y”</li>
</ul>
<p>The result looks like this:</p>
<p><img width="404" alt="Screen Shot 2022-05-28 at 11 12 23 AM" src="https://user-images.githubusercontent.com/1313787/170837955-35feb8b4-9028-4bf2-af36-9b814fc9a70c.png"></p>
<p>Moreover, the generated form is reactive: as you check or uncheck the two input checkboxes the two output checkboxes immediately update in response. In particular:</p>
<ul>
<li>the “x or y” box will be checked whenever either input box is checked</li>
<li>the “x and y” box will be checked whenever both input boxes are checked</li>
</ul>
<p><img width="136" alt="Screen Shot 2022-05-28 at 11 19 15 AM" src="https://user-images.githubusercontent.com/1313787/170838108-83610230-159e-43ea-9ef9-0e2bbebdee03.png"></p>
<p>This also all runs entirely client side, meaning that all the computation happens in your browser. Specifically:</p>
<ul>
<li>compiling the functional code to an interactive form is done client-side</li>
<li>updating form outputs in response to inputs is also done client-side</li>
</ul>
<h4 id="intelligent-forms">Intelligent forms</h4>
<p>Here’s another example that might further pique your interest:</p>
<ul>
<li><a href="https://trygrace.dev/?expression=List%252Fmap%2520%2528%255Cx%2520-%253E%2520x%2520%252B%25201%2529">Grace browser - Increment each element of a list</a></li>
</ul>
<p>The above example is a pure function to increment each element of a list:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="dt">List</span><span class="op">/</span><span class="fu">map</span> (\x <span class="ot">-></span> x <span class="op">+</span> <span class="dv">1</span>)</span></code></pre></div>
<p>The <code>List/map</code> function is unsaturated, meaning that we’re missing the final argument: the actual list to transform. So the interpreter infers the following type for the function:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="dt">List</span> <span class="dt">Natural</span> <span class="ot">-></span> <span class="dt">List</span> <span class="dt">Natural</span></span></code></pre></div>
<p>… and you can read that type as saying:</p>
<blockquote>
<p>This is a function whose input is a list of natural numbers and whose output is also a list of natural numbers.</p>
</blockquote>
<p>So what kind of interactive form will that generate?</p>
<p>The generated input to the form begins with a blue “+” button that you can click to add elements to the input list. Each time you click the button the form creates a numeric input for that list element alongside a red “-” button that you can click to delete the corresponding list element. As you add or remove elements from the input list the reactive form update will also add or remove elements from the output list, too.</p>
<p><img width="296" alt="Screen Shot 2022-05-28 at 11 14 45 AM" src="https://user-images.githubusercontent.com/1313787/170838009-3d68369d-34b0-4823-a1f9-160505c84c4e.png"></p>
<p>Moreover, each element in the input list will be a numeric input. As you adjust each input element the matching output element will automatically be set to a number that is one greater than the input number.</p>
<p>The interpreter also sets the permitted range of the numeric inputs based on the inferred type. Since the default numeric type is <code>Natural</code> (i.e. non-negative) numbers, the numeric inputs will forbid negative inputs. However, if you were to add a type annotation to specify an <code>Integer</code> element type:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="dt">List</span><span class="op">/</span><span class="fu">map</span> (\x <span class="ot">-></span> x <span class="op">+</span> <span class="dv">1</span> <span class="op">:</span> <span class="dt">Integer</span>)</span></code></pre></div>
<p>… then the generated form will change to permit negative inputs and outputs because then the inferred type would be:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="dt">List</span> <span class="dt">Integer</span> <span class="ot">-></span> <span class="dt">List</span> <span class="dt">Integer</span></span></code></pre></div>
<h4 id="shareable-forms">Shareable forms</h4>
<p>The “Grace browser” is based on the Fall-from-Grace functional programming language (or “Grace” for short), which I previously announced here:</p>
<ul>
<li><a href="https://www.haskellforall.com/2021/09/fall-from-grace-ready-to-fork.html">Fall-from-Grace: A ready-to-fork functional programming language</a></li>
</ul>
<p>… and one of the features of this language is the ability to import arbitrary expressions by URL, including functions!</p>
<p>That means that if you were to create a useful pure function for others to use you could host your code anywhere that you can serve plain text (such as a GitHub repository or gist) and anybody could turn that function into the corresponding form.</p>
<p>For example, the following gist contains a pure Grace function to compute US federal income taxes for 2022:</p>
<ul>
<li><a href="https://gist.github.com/Gabriella439/712d0648bbdcfcc83eadd0ee394beed3">GitHub Gist - Income tax calculator</a></li>
</ul>
<p>… so if you paste the URL for the raw text of the gist into the Grace browser you’ll get a shareable form for computing your taxes:</p>
<ul>
<li><a href="https://trygrace.dev/?expression=https%253A%252F%252Fgist.githubusercontent.com%252FGabriella439%252F712d0648bbdcfcc83eadd0ee394beed3%252Fraw%252F1b03f661577521b4d3dc6ca73dd11475a30c1594%252FincomeTax.ffg">Grace browser - US federal income tax calculator</a></li>
</ul>
<p><img width="390" alt="Screen Shot 2022-05-28 at 11 15 45 AM" src="https://user-images.githubusercontent.com/1313787/170838035-1b45109e-eb38-426b-9d20-0f2d216456f5.png"></p>
<p>This provides a lightweight way to publish, share, and consume utility code.</p>
<h4 id="plain-json-data">Plain JSON data</h4>
<p>The Grace browser also works for plain data, too. In fact, Grace is a superset of JSON so the Grace browser is also a JSON browser.</p>
<p>For example, I can render the JSON served at <a href="https://api.github.com"><code>https://api.github.com</code></a> as HTML by pasting that URL into the Grace browser, which produces this result:</p>
<ul>
<li><a href="https://trygrace.dev/?expression=https%253A%252F%252Fapi.github.com">Grace browser - GitHub API</a></li>
</ul>
<p><img width="814" alt="Screen Shot 2022-05-28 at 11 16 39 AM" src="https://user-images.githubusercontent.com/1313787/170838058-1e376c3a-4a02-4b81-9982-b41e07fed454.png"></p>
<p>If I’m only interested in one field of the JSON output, I can project out the field of interest like this:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>( https<span class="op">://</span>api<span class="op">.</span>github<span class="op">.</span>com<span class="op">/</span> )<span class="op">.</span>starred_gists_url</span></code></pre></div>
<ul>
<li><a href="https://trygrace.dev/?expression=%2528%2520https%253A%252F%252Fapi.github.com%252F%2520%2529.starred_gists_url">Grace browser - Query API field</a></li>
</ul>
<p><img width="505" alt="Screen Shot 2022-05-28 at 11 17 08 AM" src="https://user-images.githubusercontent.com/1313787/170838072-e4af350e-2f34-49f2-a4b1-970d11adaaea.png"></p>
<p>In other words, you can use Grace browser sort of like a “<code>jq</code> but with HTML output”.</p>
<h4 id="teaching-tool">Teaching tool</h4>
<p>You can also use the Grace browser to teach functional programming concepts, too. For example, you can illustrate the difference between Haskell/Rust-style error-handling and Go-style error-handling by entering this input into the code editor:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>{ <span class="st">"Error handling in Haskell and Rust"</span><span class="op">:</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> \input <span class="ot">-></span> input <span class="op">:</span> <span class="op"><</span> <span class="dt">Failure</span> <span class="op">:</span> <span class="dt">Text</span> <span class="op">|</span> <span class="dt">Success</span> <span class="op">:</span> <span class="dt">Natural</span> <span class="op">></span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>, <span class="st">"Error handling in Go"</span><span class="op">:</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> \input <span class="ot">-></span> input <span class="op">:</span> { <span class="st">"Failure"</span> <span class="op">:</span> <span class="dt">Optional</span> <span class="dt">Text</span>, <span class="st">"Success"</span><span class="op">:</span> <span class="dt">Optional</span> <span class="dt">Natural</span> }</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<ul>
<li><a href="https://trygrace.dev/?expression=%257B%2520%2522Error%2520handling%2520in%2520Haskell%2520and%2520Rust%2522%253A%250A%2520%2520%2520%2520%255Cinput%2520-%253E%2520input%2520%253A%2520%253C%2520Failure%2520%253A%2520Text%2520%257C%2520Success%2520%253A%2520Natural%2520%253E%250A%252C%2520%2522Error%2520handling%2520in%2520Go%2522%253A%250A%2520%2520%2520%2520%255Cinput%2520-%253E%2520input%2520%253A%2520%257B%2520%2522Failure%2522%2520%253A%2520Optional%2520Text%252C%2520%2522Success%2522%253A%2520Optional%2520Natural%2520%257D%250A%257D">Grace browser - Error handling semantics</a></li>
</ul>
<p>… and the form will illustrate how:</p>
<ul>
<li><p>In Haskell and Rust, the failure data and success data are mutually exclusive</p>
<p>The generated form illustrates this by using mutually exclusive radio buttons for the input.</p></li>
<li><p>In Go, failure data and success data are not mutually exclusive</p>
<p>The generated form illustrates this by using checkboxes to independently enable or disable the failure and success data.</p></li>
</ul>
<p><img width="620" alt="Screen Shot 2022-05-28 at 11 18 06 AM" src="https://user-images.githubusercontent.com/1313787/170838090-4ba3bcdf-a5b2-4679-9535-d9eca0257b5e.png"></p>
<h4 id="semantic-web">Semantic web</h4>
<p>Grace is currently very limited in its current incarnation, meaning that the language only provides a small set of built-in functions and operators. The reason why is because I originally created Grace to serve as a simple reference implementation of how to create a functional programming language and I intended people to fork the language to add any additional language features they needed.</p>
<p>However, if Grace were more featureful then you could imagine creating a “semantic web” of purely functional expressions that could reference each other by URL and be visualized using an intelligent client like the Grace browser. For example, you could have some pure data hosted at <code>https://example.com/students.ffg</code>:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>[ { <span class="st">"Name"</span><span class="op">:</span> <span class="st">"John Doe"</span> , <span class="st">"Grade"</span><span class="op">:</span> <span class="dv">95</span> }</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>, { <span class="st">"Name"</span><span class="op">:</span> <span class="st">"Mary Jane"</span> , <span class="st">"Grade"</span><span class="op">:</span> <span class="dv">98</span> }</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>, { <span class="st">"Name"</span><span class="op">:</span> <span class="st">"Alice Templeton"</span>, <span class="st">"Grade"</span><span class="op">:</span> <span class="dv">90</span> }</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>]</span></code></pre></div>
<p>… and then you could create a “view” into that data that adds up all the grades by hosting another expression at <code>https://example.com/total.ffg</code> which could contain:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="fu">sum</span> <span class="ot">=</span> https<span class="op">://</span>raw<span class="op">.</span>githubusercontent<span class="op">.</span>com<span class="op">/</span><span class="dt">Gabriella439</span><span class="op">/</span>grace<span class="op">/</span>main<span class="op">/</span>prelude<span class="op">/</span>natural<span class="op">/</span><span class="fu">sum</span><span class="op">.</span>ffg</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="fu">sum</span> (<span class="dt">List</span><span class="op">/</span><span class="fu">map</span> (\student <span class="ot">-></span> student<span class="op">.</span><span class="st">"Grade"</span>) <span class="op">./</span>students<span class="op">.</span>ffg)</span></code></pre></div>
<p>… and whenever you would update the data hosted at <code>students.ffg</code> the view at <code>total.ffg</code> would automatically update, too. You could then generate a web page for either view of the data using something like the Grace browser.</p>
<h4 id="conclusion">Conclusion</h4>
<p>If this interests you, the website contains a tutorial that you can try out that partially overlaps with the examples from this post:</p>
<ul>
<li><a href="https://trygrace.dev">Grace browser</a></li>
</ul>
<p>Just click the “Try the tutorial” button to give it a whirl.</p>
<p>If you want a deeper dive into what the Grace language can do, then I recommend reading the original announcement post:</p>
<ul>
<li><a href="https://www.haskellforall.com/2021/09/fall-from-grace-ready-to-fork.html">Fall-from-Grace: A ready-to-fork functional programming language</a></li>
</ul>
<p>… or reading the <code>README</code> from the corresponding GitHub project:</p>
<ul>
<li><a href="https://github.com/Gabriella439/grace#grace">GitHub - <code>Gabriella439/grace</code></a></li>
</ul>
<p>Also, I set things up so that if you do fork the language you can easily generate your own “Grace browser” for your fork of the language that’s just a bunch of static assets you can host anywhere. No server-side computation necessary!</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-13009354401398128842022-05-09T06:29:00.008-07:002022-05-09T06:54:53.606-07:00The golden rule of software distributions<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="The golden rule of software distributions" />
<meta name="twitter:description" content="A description of an architectural pattern commonly found in many software distributions" />
<title>golden-software-distro</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This is a short post documenting a pattern I learned as a user and maintainer of software distributions. I wanted to share this pattern because the lesson was non-obvious to me in my early days as a software engineer.</p>
<p>I call this pattern the “golden rule of software distributions”, which I’ll define the verbose way followed by the concise way.</p>
<p>The verbose version of the golden rule of software distributions is:</p>
<blockquote>
<p>If a package manager only permits installing or depending on one version of each of package then a software distribution for that package manager should bless one version of each package. The blessed version for each package must be compatible with the blessed version of every other package.</p>
</blockquote>
<p>The concise version of the golden rule of software distributions is:</p>
<blockquote>
<p>A locally coherent package manager requires a globally coherent software distribution.</p>
</blockquote>
<p>… where:</p>
<ul>
<li><p>“locally coherent” means that you can only install or depend on one version of each package for a given system or project</p></li>
<li><p>“globally coherent” means each package has a unique blessed version compatible with every other package’s blessed version</p></li>
</ul>
<p>Note that any sufficiently large software distribution will not perfectly adhere to this golden rule. You should view this rule as an ideal that a software distribution aspires to approximate as closely as possible, but there will necessarily be cases where they cannot.</p>
<h4 id="motivation">Motivation</h4>
<p>I’ll introduce the term “build plan” to explain the motivation behind the golden rule:</p>
<blockquote>
<p>A build plan for a package A specifies a version for each dependency of A such that A successfully builds against those dependencies.</p>
</blockquote>
<p>To motivate the golden rule, let’s examine what happens when you have a locally coherent package manager but a globally incoherent software distribution:</p>
<ul>
<li><p>Package users need to do a combinatorial search of their dependencies</p>
<p>… in order to find a successful build plan. Specifically, they may need to test multiple major versions of their direct and indirect independencies to find a permutation that successfully builds.</p></li>
<li><p>Compatible sets of packages become increasingly unlikely at scale</p>
<p>The likelihood of finding a build plan rapidly diminishes as your dependency tree grows. Beyond a certain number of dependencies a build plan might not even exist, even if every dependency is maintained.</p></li>
<li><p>Package authors need to support multiple major versions of every dependency</p>
<p>… in order to maximize the likelihood that downstream packages can find a successful build plan. Maintaining this backwards compatibility greatly increases their maintenance burden.</p></li>
<li><p>Package authors must test against multiple major versions of each dependency</p>
<p>… in order to shield their users from build failures due to unexpected build plans. This means a large number of CI runs for every proposed change to the package, which slows down their development velocity.</p></li>
<li><p>Responsibility for fixing incompatibilities becomes diffuse</p>
<p>Sometimes you need to depend on two packages (A and B) which transitively depend on incompatible versions of another package (C). Neither package A nor package B can be held responsible for fixing the problem unless there is a blessed version of package C.</p></li>
</ul>
<p>These issues lead to a lot of wasted work, which scales exponentially with the number of dependencies. Consequently, software ecosystems that ignore the golden rule run into difficulties scaling dependency trees which people will work around in the following ways:</p>
<ul>
<li><p>Culturally discouraging dependencies</p></li>
<li><p><a href="https://medium.com/plain-and-simple/dependency-vendoring-dd765be75655">Vendoring dependencies</a> within their projects</p></li>
<li><p>Gravitating towards large and monolithic dependencies / frameworks</p></li>
</ul>
<h4 id="the-fundamental-problem">The fundamental problem</h4>
<p>The golden rule is necessary because <em>build plans do not compose</em> for a locally coherent package manager. In other words, if you have a working build plan for package A and another working build plan for package B, you cannot necessarily combine those two build plans to generate a working build plan for a package that depends on both A and B. In particular, you definitely cannot combine the two build plans if A and B depend on incompatible versions of another package C.</p>
<p>However, build plans can also fail to compose for more subtle reasons. For example, you can depend on multiple packages whose build plans are all pairwise-compatible, but there still might not exist a build plan for the complete set of packages.</p>
<p>The good news is that you can trivially “weaken” a build plan, meaning that if you find a build plan that includes both packages A and B then you can downgrade that to a working build plan for just package A or just package B.</p>
<p>Consequently, the globally optimal thing to do is to find a working build plan that combines as many packages as possible, because then any subset of that build plan is still a working build plan. That ensures that any work spent fixing this larger build plan is not wasted and benefits everybody. Contrast that with work spent fixing the build for a single package (e.g. creating a lockfile), which does not benefit any other package (not even downstream packages, a.k.a. reverse dependencies).</p>
<h4 id="common-sense">Common sense?</h4>
<p>Some people might view the golden rule of software distributions as common sense that doesn’t warrant a blog post, but my experiences with the Haskell ecosystem indicate otherwise. That’s because I began using Haskell seriously around 2011, four years before <a href="https://www.fpcomplete.com/blog/2014/12/backporting-bug-fixes/">Stackage was first released</a>.</p>
<p>Before Stackage, I ran into all of the problems described in the previous section because there was no blessed set of Haskell packages. In particular, the worst problem (for me) was the inability to find a working build plan for my projects.</p>
<p>This issue went on for years; basically everyone in the Haskell ecosystem (myself included) unthinkingly cargo-culted this as the way things were supposed to work. When things went wrong we blamed Cabal (e.g. “Cabal hell”) for our problems when the root of the problem had little to do with Cabal.</p>
<p>Stackage fixed all of that when <a href="https://www.snoyman.com">Michael Snoyman</a> essentially introduced the Haskell ecosystem to the golden rule of software distributions. Stackage works by publishing a set of blessed package versions for all of the packages vetted by Stackage and these packages are guaranteed to all build together. Periodically, Stackage publishes an updated set of blessed package versions.</p>
<p>After getting used to this, I quickly converted to this way of doing things, which seemed blindingly obvious in retrospect. Also, my professional career arc shifted towards DevOps, including managing upgrades and software distributions and I discovered that this was a fairly common practice for most large software distributions.</p>
<h4 id="why-this-rule-is-not-intuitive">Why this rule is not intuitive</h4>
<p>Actually, this insight is not as obvious as people might think. In fact, a person with a superficial understanding of how software ecosystems work might suspect that the larger a software ecosystem grows the more incoherent things get. However, you actually encounter the opposite phenomenon in practice: the larger a software ecosystem gets the more coherent things get (by necessity).</p>
<p>In fact, I still see people argue against the global coherence of software ecosystems, which indicates to me that this isn’t universally received wisdom. Sometimes they argue against global coherence directly (they believe coherence imposes an undue burden on maintainers or users) or they argue against global coherence indirectly (by positing incoherence as a foundation of a larger architectural pattern). Either way, I strongly oppose global incoherence, both for the theoretical reasons outlined in this post and also based on my practical experience managing dependencies in the pre-Stackage days of the Haskell ecosystem.</p>
<p>Indeed, many of people arguing against globally coherent software ecosystems are actually unwitting beneficiaries of global coherence. There is a massive amount of invisible work that goes on behind the scenes for every software distribution to create a globally coherent package set that benefits everybody (not just the users of those software distributions). For example, all software users benefit from the work that goes into maintaining the Debian, Arch, Nixpkgs, and Brew software distributions even if they don’t specifically use those software distributions or their associated package managers.</p>
<h4 id="conclusion">Conclusion</h4>
<p>This whole post has one giant caveat, which is that all of the arguments assume that the packager manager is locally coherent, which is not always the case! In fact, there’s a post that proves that local coherence can be undesirable because it (essentially) makes dependency resolution NP complete. For more details, see:</p>
<ul>
<li><a href="https://research.swtch.com/version-sat">Version SAT</a></li>
</ul>
<p>I personally have mixed views on whether local coherence is good or bad. Right now I’m slightly on team “local coherence is good”, but my opinions on that are not fully formed, yet.</p>
<p>That said, most package managers tend to require or at least benefit from local coherence so in practice most software distributions also require global coherence. For example, Haskell’s build tooling basically requires global coherence (with some caveats I won’t go into), so global coherence is a good thing for the Haskell ecosystem.</p>
</body>
</html>Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-75688099941592912432022-05-03T19:52:00.004-07:002022-05-03T21:45:48.445-07:00Why does Haskell's take function accept insufficient elements?<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="Why does Haskell's take function accept insufficient elements?" />
<meta name="twitter:description" content="A blog post that is a long-form answer to a question originally asked on Reddit" />
<title>Why does Haskell's take function accept insufficient elements?</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This post is a long-form response to a <a href="https://www.reddit.com/r/haskell/comments/uhm04p/how_does_take_function_really_work/">question on Reddit</a>, which asked:</p>
<blockquote>
<p>I just started learning haskell, and the take function confuses me.</p>
<p>e.g <code>take 10 [1,2,3,4,5]</code> will yield <code>[1,2,3,4,5]</code></p>
<p>How does it not generate an error ?</p>
</blockquote>
<p>… and I have enough to say on this subject that I thought it would warrant a blog post rather than just a comment reply.</p>
<p>The easiest way to answer this question is to walk through all the possible alternative implementations that can fail when not given enough elements.</p>
<h4 id="solution-0-output-a-maybe">Solution 0: Output a <code>Maybe</code></h4>
<p>The first thing we could try would be to wrap the result in a <code>Maybe</code>, like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">safeTake ::</span> <span class="dt">Int</span> <span class="ot">-></span> [a] <span class="ot">-></span> <span class="dt">Maybe</span> [a]</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>safeTake <span class="dv">0</span> _ <span class="ot">=</span> <span class="dt">Just</span> []</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>safeTake n [] <span class="ot">=</span> <span class="dt">Nothing</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>safeTake n (x <span class="op">:</span> xs) <span class="ot">=</span> <span class="fu">fmap</span> (x <span class="op">:</span>) (safeTake (n <span class="op">-</span> <span class="dv">1</span>) xs)</span></code></pre></div>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> safeTake <span class="dv">3</span> [<span class="dv">0</span><span class="op">..</span>]</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Just</span> [<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">2</span>]</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> safeTake <span class="dv">3</span> []</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="dt">Nothing</span></span></code></pre></div>
<p>The main deficiency with this approach is that it is insufficiently lazy. The result will not produce a single element of the output list until <code>safeTake</code> finishes consuming the required number of elements from the input list.</p>
<p>We can see the difference with the following examples:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> oops <span class="ot">=</span> <span class="dv">1</span> <span class="op">:</span> <span class="dv">2</span> <span class="op">:</span> <span class="fu">error</span> <span class="st">"Too short"</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> <span class="fu">take</span> <span class="dv">1</span> (<span class="fu">take</span> <span class="dv">3</span> oops)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>]</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> safeTake <span class="dv">1</span> <span class="op">=<<</span> safeTake <span class="dv">3</span> oops</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="op">***</span> <span class="dt">Exception</span><span class="op">:</span> <span class="dt">Too</span> short</span></code></pre></div>
<h4 id="solution-1-fail-with-error">Solution 1: Fail with <code>error</code></h4>
<p>Another approach would be to create a <a href="https://wiki.haskell.org/Partial_functions">partial</a> function that fails with an <code>error</code> if we run out of elements, like this:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ot">partialTake ::</span> <span class="dt">Int</span> <span class="ot">-></span> [a] <span class="ot">-></span> [a]</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>partialTake <span class="dv">0</span> _ <span class="ot">=</span> []</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>partialTake n (x <span class="op">:</span> xs) <span class="ot">=</span> x <span class="op">:</span> partialTake (n <span class="op">-</span> <span class="dv">1</span>) xs</span></code></pre></div>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> partialTake <span class="dv">3</span> [<span class="dv">0</span><span class="op">..</span>]</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">2</span>]</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> partialTake <span class="dv">3</span> []</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">*</span><span class="dt">Main</span><span class="op">></span> partialTake <span class="dv">3</span> []</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="op">***</span> <span class="dt">Exception</span><span class="op">:</span> Test.hs<span class="op">:</span>(<span class="dv">7</span>,<span class="dv">1</span>)<span class="op">-</span>(<span class="dv">8</span>,<span class="dv">51</span>)<span class="op">:</span> <span class="dt">Non</span><span class="op">-</span>exhaustive patterns <span class="kw">in</span> function partialTake</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> partialTake <span class="dv">1</span> (partialTake <span class="dv">3</span> oops)</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>]</span></code></pre></div>
<p>Partial functions like these are undesirable, though, so we won’t go with that solution.</p>
<h4 id="solution-2-use-a-custom-list-like-type">Solution 2: Use a custom list-like type</h4>
<p>Okay, but what if we could store a value at the end of the list indicating whether or not the <code>take</code> succeeded. One way we could do that would be to define an auxiliary type similar to a list, like this:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DeriveFoldable #-}</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">ListAnd</span> r a <span class="ot">=</span> <span class="dt">Cons</span> a (<span class="dt">ListAnd</span> r a) <span class="op">|</span> <span class="dt">Nil</span> r <span class="kw">deriving</span> (<span class="dt">Foldable</span>, <span class="dt">Show</span>)</span></code></pre></div>
<p>… where now the empty (<code>Nil</code>) constructor can store an auxiliary value. We can then use this auxiliary value to indicate to the user whether or not the function succeeded or not:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Result</span> <span class="ot">=</span> <span class="dt">Sufficient</span> <span class="op">|</span> <span class="dt">Insufficient</span> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="ot">takeAnd ::</span> <span class="dt">Int</span> <span class="ot">-></span> [a] <span class="ot">-></span> <span class="dt">ListAnd</span> <span class="dt">Result</span> a</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>takeAnd <span class="dv">0</span> _ <span class="ot">=</span> <span class="dt">Nil</span> <span class="dt">Sufficient</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>takeAnd n [] <span class="ot">=</span> <span class="dt">Nil</span> <span class="dt">Insufficient</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>takeAnd n (x <span class="op">:</span> xs) <span class="ot">=</span> <span class="dt">Cons</span> x (takeAnd (n <span class="op">-</span> <span class="dv">1</span>) xs)</span></code></pre></div>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> takeAnd <span class="dv">3</span> [<span class="dv">0</span><span class="op">..</span>]</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="dt">Cons</span> <span class="dv">0</span> (<span class="dt">Cons</span> <span class="dv">1</span> (<span class="dt">Cons</span> <span class="dv">2</span> (<span class="dt">Nil</span> <span class="dt">Sufficient</span>)))</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> takeAnd <span class="dv">3</span> []</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="dt">Nil</span> <span class="dt">Insufficient</span></span></code></pre></div>
<p>Also, the <code>ListAnd</code> type derives <code>Foldable</code>, so we can recover the old behavior by converting the <code>ListAnd Result a</code> type into <code>[a]</code> using <code>toList</code>:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> <span class="kw">import</span> <span class="dt">Data.Foldable</span> (toList)</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> toList (takeAnd <span class="dv">3</span> [<span class="dv">0</span><span class="op">..</span>])</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>[<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">2</span>]</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> toList (takeAnd <span class="dv">3</span> [])</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>[]</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> toList (takeAnd <span class="dv">1</span> (toList (takeAnd <span class="dv">3</span> oops)))</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>]</span></code></pre></div>
<p>This is the first total function that has the desired laziness characteristics, but the downside is that the <code>take</code> function now has a much weirder type. Can we solve this only using existing types from <code>base</code>?</p>
<h4 id="solution-3-return-a-pair">Solution 3: Return a pair</h4>
<p>Well, what if we were to change the type of <code>take</code> to return an pair containing an ordinary list alongside a <code>Result</code>, like this:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ot">takeWithResult ::</span> <span class="dt">Int</span> <span class="ot">-></span> [a] <span class="ot">-></span> ([a], <span class="dt">Result</span>)</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>takeWithResult <span class="dv">0</span> _ <span class="ot">=</span> ( [], <span class="dt">Sufficient</span> )</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a>takeWithResult n [] <span class="ot">=</span> ( [], <span class="dt">Insufficient</span>)</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a>takeWithResult n (x <span class="op">:</span> xs) <span class="ot">=</span> (x <span class="op">:</span> ys, result )</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> (ys, result) <span class="ot">=</span> takeWithResult (n <span class="op">-</span> <span class="dv">1</span>) xs</span></code></pre></div>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> takeWithResult <span class="dv">3</span> [<span class="dv">0</span><span class="op">..</span>]</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>([<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">2</span>],<span class="dt">Sufficient</span>)</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> takeWithResult <span class="dv">3</span> []</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>([],<span class="dt">Insufficient</span>)</span></code></pre></div>
<p>Now we don’t need to add this weird <code>ListAnd</code> type to <code>base</code>, and we can recover the old behavior by post-processing the output using <code>fst</code>:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> <span class="fu">fst</span> (takeWithResult <span class="dv">3</span> [<span class="dv">0</span><span class="op">..</span>])</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">2</span>]</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="fu">fst</span> (takeWithResult <span class="dv">3</span> [])</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>[]</span></code></pre></div>
<p>… and this also has the right laziness characteristics:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="op">>>></span> <span class="fu">fst</span> (takeWithResult <span class="dv">1</span> (<span class="fu">fst</span> (takeWithResult <span class="dv">3</span> oops)))</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>]</span></code></pre></div>
<p>… and we can replace <code>Result</code> with a <code>Bool</code> if want a solution that depends solely on types from <code>base</code>:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ot">takeWithResult ::</span> <span class="dt">Int</span> <span class="ot">-></span> [a] <span class="ot">-></span> ([a], <span class="dt">Bool</span>)</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>takeWithResult <span class="dv">0</span> _ <span class="ot">=</span> ([], <span class="dt">True</span>)</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a>takeWithResult n [] <span class="ot">=</span> ([], <span class="dt">False</span>)</span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a>takeWithResult n (x <span class="op">:</span> xs) <span class="ot">=</span> (x <span class="op">:</span> ys, result)</span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a> (ys, result) <span class="ot">=</span> takeWithResult (n <span class="op">-</span> <span class="dv">1</span>) xs</span></code></pre></div>
<p>However, even this solution is not completely satisfactory. There’s nothing that forces the user to check the <code>Bool</code> value before accessing the list, so this is not as safe as, say, the <code>safeTake</code> function which returns a <code>Maybe</code>. The <code>Bool</code> included in the result is more of an informational value rather than a safeguard.</p>
<h4 id="conclusion">Conclusion</h4>
<p>So the long-winded answer to the original question is that there are several alternative ways we could implement <code>take</code> that can fail if the input list is too small, but in my view each of them has their own limitations.</p>
<p>This is why I think Haskell’s current <code>take</code> function is probably the least worst of the alternatives, even if it’s not the safest possible implementation.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com2tag:blogger.com,1999:blog-1777990983847811806.post-2920752981655814322022-05-01T08:50:00.001-07:002022-05-01T08:57:51.027-07:00Introductory resources to type theory for language implementers<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="Introductory resources to type theory for language implementers" />
<meta name="twitter:description" content="A post touring resources you can use to learn how to implement a type checker or type inference algorithm" />
<title>Introductory resources to type theory for language implementers</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>This post briefly tours resources that helped introduce me to type theory, because I’m frequently asked by others for resources on this subject (even though I never had a formal education in type theory). Specifically, these resources will focus more on how to implement a type checker or type inference algorithm.</p>
<p>Also, my post will be biased against books, because I don’t tend to learn well from reading books. That said, I will mention a few books that I’ve heard others recommend, even if I can’t personally vouch for them.</p>
<h4 id="what-worked-for-me">What worked for me</h4>
<p>The first and most important resource that I found useful was this one:</p>
<ul>
<li><a href="https://www.andres-loeh.de/LambdaPi/LambdaPi.pdf">A tutorial implementation of a dependently typed lambda calculus</a></li>
</ul>
<p>The reason why is because that paper shows logical notation side-by-side with Haskell code. That juxtaposition served as a sort of “Rosetta stone” for me to understand the correspondence between logical judgments and code. The paper also introduces some type theory basics (and dependent types!).</p>
<p>Along similar lines, another helpful resource was:</p>
<ul>
<li><a href="https://web.cecs.pdx.edu/~mpj/thih/thih.pdf">Typing Haskell in Haskell</a></li>
</ul>
<p>… which, as the name suggests, walks through a Haskell program to type-check Haskell code. This paper along with the preceding one helped bring type checkers “down to earth” to me by showing how there wasn’t any magic or secret sauce to implementing a type checker.</p>
<p>After that, the next thing that helped me improve my understanding was learning about <a href="https://en.wikipedia.org/wiki/Pure_type_system">pure type systems</a>. Specifically, this paper was a very clear introduction to pure type systems:</p>
<ul>
<li><a href="https://www.microsoft.com/en-us/research/wp-content/uploads/1997/01/henk.pdf">Henk: a typed intermediate language</a></li>
</ul>
<p>You can think of pure type systems as sort of a “framework” for specifying type systems or talking about them. For example, the simply typed lambda calculus, System F, System FΩ, and the calculus of constructions are some example type systems that you’ll hear the literature refer to, and they’re all special cases of this general framework. You can think of pure type systems as generalizing the <a href="https://en.wikipedia.org/wiki/Lambda_cube">lambda cube</a>.</p>
<p>However, none of the above resources introduce how to implement a type system with “good” type inference. To elaborate on that, many simple type systems can infer the types of program outputs from program inputs, but cannot work “in reverse” and infer the types of inputs from outputs. Hindley Milner type inference is one example of a “good” type inference algorithm that can work in reverse.</p>
<p>However, I never learned Hindley Milner type inference all that well, because I skipped straight to bidirectional type checking, which is described in this paper:</p>
<ul>
<li><a href="https://www.cl.cam.ac.uk/~nk480/bidir.pdf">Complete and Easy Bidirectional Typechecking for Higher-Rank Polymorphism</a></li>
</ul>
<p>I prefer bidirectional type checking because (in my limited experience) it’s easier to extend the bidirectional type checking algorithm with new language features (or, at least, easier than extending Hindley Milner with the same language features).</p>
<p>The other reason I’m a fan of bidirectional type checking is that many cutting edge advances in research slot in well to a bidirectional type checker or even explicitly present their research using the framework of bidirectional type checking.</p>
<h4 id="books">Books</h4>
<p>Like I mentioned, I didn’t really learn that much from books, but here are some books that I see others commonly recommend, even if I can’t personally vouch for them:</p>
<ul>
<li><p><a href="https://www.cis.upenn.edu/~bcpierce/tapl/">Types and Programming Languages</a></p></li>
<li><p><a href="https://www.cs.cmu.edu/~rwh/pfpl/">Practical Foundations for Programming Languages</a></p></li>
</ul>
<h4 id="example-code">Example code</h4>
<p>I also created a tutorial implementation of a functional programming language that summarizes everything I know about programming language theory so far, which is my Fall-from-Grace project:</p>
<ul>
<li><a href="https://github.com/Gabriel439/grace">GitHub - Gabriel439/grace</a></li>
</ul>
<p>This project is a clean reference implementation of how to implement an interpreted langauge using (what I believe are) best practices in the Haskell ecosystem.</p>
<p>I also have a longer post explaining the motivation behind the above project:</p>
<ul>
<li><a href="https://www.haskellforall.com/2021/09/fall-from-grace-ready-to-fork.html">Fall-from-Grace: A ready-to-fork functional programming language</a></li>
</ul>
<h4 id="conclusion">Conclusion</h4>
<p>Note that these are not the only resources that I learned from. This post only summarizes the seminal resources that greatly enhanced my understanding of all other resources.</p>
<p>Feel free to leave a comment if you have any other resources that you’d like to suggest that you feel were helpful in this regard.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-69619157502401235202022-03-29T07:14:00.001-07:002022-03-29T07:14:24.842-07:00Modeling PlusCal in Haskell using Cartesian products of NFAs<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="Modeling PlusCal in Haskell using Cartesian products of NFAs" />
<meta name="twitter:description" content="This post introduces a Haskell eDSL implementing a subset of PlusCal along with a tour of the implementation" />
<title>Modeling PlusCal in Haskell using Cartesian products of NFAs</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p><a href="https://en.wikipedia.org/wiki/PlusCal">PlusCal</a> is a formal specification language created to model concurrent systems, and recently I became obsessed with implementing PlusCal as an embedded domain-specific language (eDSL) in Haskell. In other words, I want to model PlusCal processes as Haskell subroutines and also implement the model checker in Haskell.</p>
<p>I’m not done implementing this PlusCal eDSL, but I’m writing this to share what I learned in the course of doing so. Specifically, what I learned was:</p>
<ul>
<li><p>You can model individual PlusCal processes as non-deterministic finite-state automata (NFAs)</p>
<p>I believe this is well-understood by users of PlusCal, but I’m mentioning this for people who are new to PlusCal.</p></li>
<li><p>You can model concurrent processes in PlusCal by computing the Cartesian product of their NFAs</p>
<p>I’m not sure if this is widely understood or not.</p></li>
<li><p>Haskell’s <code>Applicative</code> abstraction can compute Cartesian products of NFAs</p>
<p>This is the novel insight that this post contributes.</p></li>
</ul>
<p>There are two reasons why I’m keen on modeling PlusCal using Cartesian products of NFAs:</p>
<ul>
<li><p>We can use this trick to combine any number of PlusCal processes into a composite process</p>
<p>The behavior of this composite process is indistinguishable from the original collection of processes.</p></li>
<li><p>This approach simplifies the implementation of the model checker</p>
<p>The model checker now only needs to accept a single process as its input. To model check more than one process you compose them into a single process and model check the composite process.</p></li>
</ul>
<p>Also, it’s a theoretically elegant solution, and that’s good enough for me.</p>
<h4 id="scope">Scope</h4>
<p>Like I mentioned before, I have not fully implemented all of PlusCal, but here are the features I will explain how to implement in this post:</p>
<ul>
<li><p>Concurrent processes</p>
<p>Concurrent processes in PlusCal are essentially NFAs where PlusCal labels correspond to NFA states and state transitions represent atomic process changes.</p>
<p>To be pedantic, a PlusCal process is an NFA where the transitions are not labeled (or, equivalently, there is only one input symbol).</p></li>
<li><p>Labels</p>
<p>These are breakpoints within a PlusCal process where the process may be interrupted by another process. All other actions are uninterruptible, so anything that happens between two successive labels is atomic in PlusCal.</p>
<p>Note that a label in PlusCal is not the same as a label in traditional NFA terminology. PlusCal labels correspond to NFA states.</p></li>
<li><p>The <code>either</code> keyword</p>
<p>PlusCal lets a process non-deterministically branch using the <code>either</code> keyword with one nested subroutine per branch. The model checker explores and verify all possible branches.</p></li>
<li><p>The <code>with</code> keyword</p>
<p>A process can non-deterministically choose from zero or more values. Just like with <code>either</code> the model checker explores and verifies all possible choices.</p></li>
<li><p>The <code>await</code> keyword</p>
<p>A process can wait until a condition is satisfied before proceeding using the <code>await</code> keyword.</p></li>
<li><p>The <code>while</code> keyword</p>
<p>A process can run in a loop until a condition is satisfied</p></li>
<li><p>The <code>skip</code> keyword</p>
<p>A process can <code>skip</code>, which does nothing.</p></li>
</ul>
<p>I am not (yet) explaining how to implement the following parts of <code>PlusCal</code>:</p>
<ul>
<li><p>Global or process-local state</p></li>
<li><p>Temporal expressions</p>
<p>Note that there is a Haskell package named <a href="https://github.com/awakesecurity/spectacle"><code>spectacle</code></a> for temporal logic in Haskell that a coworker of mine published, which I might be able to use for this purpose, but I haven’t attempted to incorporate that, yet.</p></li>
<li><p>The <code>print</code> keyword</p></li>
<li><p>The <code>assert</code> keyword</p></li>
<li><p>The <code>goto</code> keyword</p></li>
<li><p>The model checker</p>
<p>We need to be able to model temporal expressions in order to implement the model checker and I haven’t yet incorporated temporal expressions into my implementation.</p></li>
</ul>
<p>I believe the implementation I describe in this post can be extended with those missing features (with the exception of <code>goto</code>) and I’ve already privately implemented some of them. However, I omit them because they would interfere with the subject of this post.</p>
<p>Also, some PlusCal features we’ll get for free by virtue of embedding PlusCal in Haskell:</p>
<ul>
<li><p>(Non-temporal) Expressions</p>
<p>We get all of TLA+’s non-temporal functionality (e.g. scalars, functions, composite data structures) for free from the Haskell language and its standard library. Plus we also access new functionality (such as algebraic data types) via Haskell.</p></li>
<li><p>Procedures (including the <code>call</code> and <code>return</code> keyword)</p>
<p>We won’t need to explicitly implement support for these keywords. We can already define and invoke procedures in Haskell using <code>do</code> notation, which we can overload to work with PlusCal processes.</p></li>
<li><p>Modules</p>
<p>We can model PlusCal modules using a combination of Haskell modules and Haskell functions.</p></li>
</ul>
<h4 id="user-experience">User experience</h4>
<p>The best way to illustrate what I have in mind is through some sample working Haskell code.</p>
<p>First, I’d like to be able to translate a PlusCal process like this:</p>
<pre><code>begin
A:
skip;
B:
either
C:
skip;
or
skip;
end either;
D:
skip;
end process</code></pre>
<p>… into a Haskell process like this:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">strings ::</span> <span class="dt">Coroutine</span> <span class="dt">String</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>strings <span class="ot">=</span> <span class="dt">Begin</span> <span class="st">"A"</span> <span class="kw">do</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> yield <span class="st">"B"</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">either</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> [ yield <span class="st">"C"</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> , skip</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> yield <span class="st">"D"</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> end</span></code></pre></div>
<p>The type of our <code>strings</code> process is <code>Coroutine String</code>, meaning that it is a <code>Coroutine</code> whose labels are <code>String</code>s. The reason we specify the type of the labels is because the Haskell eDSL permits labels of any type and these labels might not necessarily be strings.</p>
<p>For example, suppose PlusCal permitted integers for labels, like this:</p>
<pre><code>begin
0:
with x \in { 1, 2, 3 } do
await (x % 2 = 0);
x: (* Suppose we can even create dynamic labels from values in scope *)
skip;
end process</code></pre>
<p>Then, the equivalent Haskell process would be:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ot">ints ::</span> <span class="dt">Coroutine</span> <span class="dt">Int</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>ints <span class="ot">=</span> <span class="dt">Begin</span> <span class="dv">0</span> <span class="kw">do</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> x <span class="ot"><-</span> with [ <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span> ]</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> await (<span class="fu">even</span> x)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> yield x</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> end</span></code></pre></div>
<p>I mentioned in the introduction that we will be able to combine processes into a composite process, which looks like this:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ot">pairs ::</span> <span class="dt">Coroutine</span> (<span class="dt">String</span>, <span class="dt">Int</span>)</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>pairs <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> string <span class="ot"><-</span> strings</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> int <span class="ot"><-</span> ints</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (string, int)</span></code></pre></div>
<p>The Haskell code evaluates those three processes to canonical normal forms representing the evolution of labels for each process.</p>
<p>For example, the canonical normal form for the <code>strings</code> process is:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span> <span class="st">"A"</span> [ <span class="dt">Yield</span> <span class="st">"B"</span> [ <span class="dt">Yield</span> <span class="st">"C"</span> [ <span class="dt">Yield</span> <span class="st">"D"</span> [] ] , <span class="dt">Yield</span> <span class="st">"D"</span> [] ] ]</span></code></pre></div>
<p>… representing the following NFA with these labels and transitions:</p>
<pre><code>A → B → D
↘ ↗
C</code></pre>
<p>In other words, this normal form data structure uses lists to model non-determinism (one list element per valid transition) and nesting to model sequential transitions.</p>
<p>Similarly, the canonical normal form for the <code>ints</code> process is:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span> <span class="dv">0</span> [ <span class="dt">Yield</span> <span class="dv">2</span> [] ]</span></code></pre></div>
<p>… representing the following NFA with these labels and transitions:</p>
<pre><code>0
↓
2</code></pre>
<p>Finally, the canonical normal form for the composite <code>pairs</code> process is:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span> ( <span class="st">"A"</span> , <span class="dv">0</span> )</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"B"</span> , <span class="dv">0</span> )</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">0</span> )</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">0</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">2</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">0</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"B"</span> , <span class="dv">2</span> )</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">2</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) []</span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"A"</span> , <span class="dv">2</span> )</span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"B"</span> , <span class="dv">2</span> )</span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">2</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) []</span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a> ]</span></code></pre></div>
<p>… which is the Cartesian product of the two smaller NFAs:</p>
<pre><code>(A,0) → (B,0) → (D,0)
↘ ↗
(C,0)
↓ ↓ ↓ ↓
(C,2)
↗ ↘
(A,2) → (B,2) → (D,2)</code></pre>
<p>… and if we convert the Haskell composite process to the equivalent PlusCal process we would get:</p>
<pre><code>begin
(A,0):
skip;
either
(B,0):
either
(C,0):
either
(D,0):
skip;
(D,2):
skip;
or
(C,2):
skip;
(D,2):
skip;
end either;
or
(D,0):
skip;
(D,2):
skip;
end either;
or
(A,2):
skip;
(B,2):
skip;
either
(C,2):
skip;
(D,2):
skip;
or
(D,2):
skip;
end either;
end either;
end process</code></pre>
<p>This composite process is indistinguishable from the two input processes, meaning that:</p>
<ul>
<li><p>This composite process is interruptible whenever one of the two original input processes is interruptible</p></li>
<li><p>This composite process performs an atomic state transition whenever one of the two input processes performs a state transition</p></li>
<li><p>The current label of the composite process is a function of the current label of the two original input processes</p></li>
</ul>
<h4 id="the-process-type">The <code>Process</code> type</h4>
<p>Now I’ll explain how to implement this subset of PlusCal as an eDSL in Haskell.</p>
<p>First, we begin from the following two mutually-recursive types which represent NFAs where there are zero more labeled states but the transitions are unlabeled:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Process</span> label result <span class="ot">=</span> <span class="dt">Choice</span> [<span class="dt">Step</span> label result]</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Step</span> label result <span class="ot">=</span> <span class="dt">Yield</span> label (<span class="dt">Process</span> label result) <span class="op">|</span> <span class="dt">Done</span> result</span></code></pre></div>
<p>The <code>Process</code> type handles the non-deterministic transitions for our NFA: a <code>Process</code> stores zero or more valid state transitions (represented as a list of valid next <code>Step</code>s).</p>
<p>The <code>Step</code> type handles the labeled states for our NFA: a <code>Step</code> can either <code>Yield</code> or be <code>Done</code>:</p>
<ul>
<li><p>A step that <code>Yield</code>s includes the label for the current state and the remainder of the <code>Process</code></p></li>
<li><p>A step that is <code>Done</code> includes a <code>result</code></p>
<p>This <code>result</code> is used to thread data bound by one step to subsequent steps. For example, this is how our previous example was able to pass the <code>x</code> bound by <code>with</code> to the subsequent <code>yield</code> command:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a>ints <span class="ot">=</span> <span class="dt">Begin</span> <span class="dv">0</span> <span class="kw">do</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a> x <span class="ot"><-</span> with [ <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span> ]</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a> …</span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a> yield x</span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> …</span></code></pre></div></li>
</ul>
<p>We’ll add support for rendering the data structures for debugging purposes:</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DerivingStrategies #-}</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE GeneralizedNewtypeDeriving #-}</span></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE UndecidableInstances #-}</span></span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">GHC.Exts</span> (<span class="dt">IsList</span>(..))</span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Process</span> label result <span class="ot">=</span> <span class="dt">Choice</span> [<span class="dt">Step</span> label result]</span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">IsList</span>, <span class="dt">Show</span>)</span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Step</span> label result <span class="ot">=</span> <span class="dt">Yield</span> label (<span class="dt">Process</span> label result) <span class="op">|</span> <span class="dt">Done</span> result</span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span></code></pre></div>
<p>I make use of a small trick here where I derive <code>IsList</code> for the <code>Process</code> type This lets can omit the <code>Choice</code> constructor when creating or rendering values of type <code>Process</code>. That simplifies Haskell code like this:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span> <span class="st">"A"</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> <span class="st">"B"</span></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> <span class="st">"C"</span> (<span class="dt">Choice</span> [ <span class="dt">Yield</span> <span class="st">"D"</span> (<span class="dt">Choice</span> []) ])</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> <span class="st">"D"</span> (<span class="dt">Choice</span> [])</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a> ])</span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a> ])</span></code></pre></div>
<p>… to this more ergonomic syntax if we enable the <code>OverloadedLists</code> extension:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span> <span class="st">"A"</span> [ <span class="dt">Yield</span> <span class="st">"B"</span> [ <span class="dt">Yield</span> <span class="st">"C"</span> [ <span class="dt">Yield</span> <span class="st">"D"</span> [] ] , <span class="dt">Yield</span> <span class="st">"D"</span> [] ] ]</span></code></pre></div>
<h4 id="do-notation"><code>do</code> notation</h4>
<p>However, we don’t expect users to author these data structures by hand. Instead, we can use Haskell’s support for overloading <code>do</code> notation so that users can author <code>Process</code> values using subroutine-like syntax. In order to do this we need to implement the <code>Monad</code> class for <code>Process</code>, like this:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE BlockArguments #-}</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Control.Monad</span> <span class="kw">as</span> <span class="dt">Monad</span></span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monad</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb18-6"><a href="#cb18-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">Choice</span> ps <span class="op">>>=</span> f <span class="ot">=</span> <span class="dt">Choice</span> <span class="kw">do</span></span>
<span id="cb18-7"><a href="#cb18-7" aria-hidden="true" tabindex="-1"></a> p <span class="ot"><-</span> ps</span>
<span id="cb18-8"><a href="#cb18-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> p <span class="kw">of</span></span>
<span id="cb18-9"><a href="#cb18-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> label rest <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb18-10"><a href="#cb18-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (<span class="dt">Yield</span> label (rest <span class="op">>>=</span> f))</span>
<span id="cb18-11"><a href="#cb18-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">Done</span> result <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb18-12"><a href="#cb18-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> <span class="dt">Choice</span> possibilities <span class="ot">=</span> f result</span>
<span id="cb18-13"><a href="#cb18-13" aria-hidden="true" tabindex="-1"></a> possibilities</span>
<span id="cb18-14"><a href="#cb18-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-15"><a href="#cb18-15" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb18-16"><a href="#cb18-16" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> result <span class="ot">=</span> <span class="dt">Choice</span> (<span class="fu">pure</span> (<span class="dt">Done</span> result))</span>
<span id="cb18-17"><a href="#cb18-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-18"><a href="#cb18-18" aria-hidden="true" tabindex="-1"></a> (<span class="op"><*></span>) <span class="ot">=</span> <span class="dt">Monad</span><span class="op">.</span>ap</span>
<span id="cb18-19"><a href="#cb18-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-20"><a href="#cb18-20" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Functor</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb18-21"><a href="#cb18-21" aria-hidden="true" tabindex="-1"></a> <span class="fu">fmap</span> <span class="ot">=</span> <span class="dt">Monad</span><span class="op">.</span>liftM</span></code></pre></div>
<p>… and we also need to provide the following utility function:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="ot">yield ::</span> label <span class="ot">-></span> <span class="dt">Process</span> label ()</span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a>yield label <span class="ot">=</span> <span class="dt">Choice</span> [<span class="dt">Yield</span> label (<span class="fu">pure</span> ())]</span></code></pre></div>
<p>Equipped with that <code>Monad</code> instance we can now author code like this:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Process</span> <span class="dt">Int</span> ()</span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">1</span></span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">2</span></span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">3</span></span></code></pre></div>
<p>… and that superficially imperative code actually evaluates to a pure data structure:</p>
<div class="sourceCode" id="cb21"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a>[ <span class="dt">Yield</span> <span class="dv">1</span> [ <span class="dt">Yield</span> <span class="dv">2</span> [ <span class="dt">Yield</span> <span class="dv">3</span> [ <span class="dt">Done</span> () ] ] ] ]</span></code></pre></div>
<p>The reason that works is because any type that implements <code>Monad</code> can be sequenced using <code>do</code> notation, because <code>do</code> notation is syntactic sugar for the <code>(>>=)</code> operator we defined in our <code>Monad</code> instance. To learn more, read:</p>
<ul>
<li><a href="https://en.wikibooks.org/wiki/Haskell/do_notation">Haskell wikibook - <code>do</code> notation</a></li>
</ul>
<p>We can also now implement <code>skip</code>, which is just a synonym for <code>pure ()</code> (i.e. “do nothing”):</p>
<div class="sourceCode" id="cb22"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="ot">skip ::</span> <span class="dt">Process</span> label ()</span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a>skip <span class="ot">=</span> <span class="fu">pure</span> ()</span></code></pre></div>
<p>Unlike PlusCal, we don’t need to use <code>skip</code> most of the time. In particular, we don’t need to add a skip in between two labels if nothing happens. For example, suppose we try to translate this PlusCal code:</p>
<pre><code>1:
skip;
2:
skip;</code></pre>
<p>… to Haskell:</p>
<div class="sourceCode" id="cb24"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Process</span> <span class="dt">Int</span> ()</span>
<span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">1</span></span>
<span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a> skip</span>
<span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">2</span></span>
<span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a> skip</span></code></pre></div>
<p>We don’t actually need those <code>skip</code>s. The following code without <code>skip</code>s is the exact same <code>Process</code>:</p>
<div class="sourceCode" id="cb25"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Process</span> <span class="dt">Int</span> ()</span>
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">1</span></span>
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">2</span></span></code></pre></div>
<p>Both ways of writing the <code>Process</code> produce the same result, which is:</p>
<div class="sourceCode" id="cb26"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a>[ <span class="dt">Yield</span> <span class="dv">1</span> [ <span class="dt">Yield</span> <span class="dv">2</span> [ <span class="dt">Done</span> () ] ] ]</span></code></pre></div>
<p>Finally, we can implement the <code>while</code> keyword as an ordinary Haskell function:</p>
<div class="sourceCode" id="cb27"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a><span class="ot">while ::</span> <span class="dt">Process</span> label <span class="dt">Bool</span> <span class="ot">-></span> <span class="dt">Process</span> label () <span class="ot">-></span> <span class="dt">Process</span> label ()</span>
<span id="cb27-2"><a href="#cb27-2" aria-hidden="true" tabindex="-1"></a>while condition body <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb27-3"><a href="#cb27-3" aria-hidden="true" tabindex="-1"></a> bool <span class="ot"><-</span> condition</span>
<span id="cb27-4"><a href="#cb27-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">Monad</span><span class="op">.</span>when bool <span class="kw">do</span></span>
<span id="cb27-5"><a href="#cb27-5" aria-hidden="true" tabindex="-1"></a> body</span>
<span id="cb27-6"><a href="#cb27-6" aria-hidden="true" tabindex="-1"></a> while condition body</span></code></pre></div>
<p>In fact, there’s nothing really PlusCal-specific about this utility; this functionality already exists as <code>Control.Monad.Loops.whileM_</code>.</p>
<h4 id="alternation">Alternation</h4>
<p>So far we can only sequence commands, but we’d also like to be able to branch non-deterministically. Fortunately, Haskell has a standard API for doing that, too, which is the <code>Alternative</code> class. We can implement <code>Alternative</code> for our <code>Process</code> type like this:</p>
<div class="sourceCode" id="cb28"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Applicative</span> (<span class="dt">Alternative</span>(..))</span>
<span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Alternative</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a> empty <span class="ot">=</span> <span class="dt">Choice</span> empty</span>
<span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">Choice</span> stepsL <span class="op"><|></span> <span class="dt">Choice</span> stepsR <span class="ot">=</span> <span class="dt">Choice</span> (stepsL <span class="op"><|></span> stepsR)</span></code></pre></div>
<p>We’ll also define <code>end</code> to be a synonym for <code>empty</code>:</p>
<div class="sourceCode" id="cb29"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="ot">end ::</span> <span class="dt">Process</span> label a</span>
<span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a>end <span class="ot">=</span> empty</span></code></pre></div>
<p>In other words, if you <code>end</code> a <code>Process</code> there are no more valid transitions.</p>
<p>When we implement this <code>Alternative</code> instance we can make use of several general-purpose utilities that work for any type that implements <code>Alternative</code>. One such utility is <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/Data-Foldable.html#v:asum"><code>Data.Foldable.asum</code></a>, which behaves exactly the same as PlusCal’s <code>either</code> keyword:</p>
<div class="sourceCode" id="cb30"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span> <span class="kw">hiding</span> (either)</span>
<span id="cb30-2"><a href="#cb30-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb30-3"><a href="#cb30-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Foldable</span> <span class="kw">as</span> <span class="dt">Foldable</span></span>
<span id="cb30-4"><a href="#cb30-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb30-5"><a href="#cb30-5" aria-hidden="true" tabindex="-1"></a><span class="fu">either</span><span class="ot"> ::</span> [<span class="dt">Process</span> label result] <span class="ot">-></span> <span class="dt">Process</span> label result</span>
<span id="cb30-6"><a href="#cb30-6" aria-hidden="true" tabindex="-1"></a><span class="fu">either</span> <span class="ot">=</span> <span class="dt">Foldable</span><span class="op">.</span>asum</span></code></pre></div>
<p>We could also write the implementation of <code>either</code> by hand if we wanted to, which would be:</p>
<div class="sourceCode" id="cb31"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb31-1"><a href="#cb31-1" aria-hidden="true" tabindex="-1"></a><span class="fu">either</span> (process <span class="op">:</span> processes) <span class="ot">=</span> process <span class="op"><|></span> <span class="fu">either</span> processes</span>
<span id="cb31-2"><a href="#cb31-2" aria-hidden="true" tabindex="-1"></a><span class="fu">either</span> [] <span class="ot">=</span> empty</span></code></pre></div>
<p>… which is exact same behavior as using <code>asum</code>.</p>
<p>In other words, given a list of <code>Process</code>es, we can try all of them in parallel using <code>either</code>. All that <code>either</code> does is concatenate the lists of valid <code>Step</code>s for each of the input <code>Process</code>es.</p>
<p><a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/Control-Monad.html#v:guard"><code>Control.Monad.guard</code></a> is another utility we get for free by virtue of implementing <code>Alternative</code> and <code>guard</code> behaves in the exact same way as PlusCal’s <code>await</code> keyword:</p>
<div class="sourceCode" id="cb32"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a><span class="ot">await ::</span> <span class="dt">Bool</span> <span class="ot">-></span> <span class="dt">Process</span> label ()</span>
<span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a>await <span class="ot">=</span> <span class="dt">Monad</span><span class="op">.</span>guard</span></code></pre></div>
<p>We could also write the implementation of <code>await</code> by hand if we wanted to, which would be:</p>
<div class="sourceCode" id="cb33"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb33-1"><a href="#cb33-1" aria-hidden="true" tabindex="-1"></a>await <span class="dt">True</span> <span class="ot">=</span> skip</span>
<span id="cb33-2"><a href="#cb33-2" aria-hidden="true" tabindex="-1"></a>await <span class="dt">False</span> <span class="ot">=</span> end</span></code></pre></div>
<p>… which is the same behavior as using <code>guard</code>.</p>
<p>Finally, we can implement <code>with</code> in terms of <code>either</code>, like this:</p>
<div class="sourceCode" id="cb34"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="ot">with ::</span> [result] <span class="ot">-></span> <span class="dt">Process</span> label result</span>
<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a>with results <span class="ot">=</span> <span class="fu">either</span> (<span class="fu">map</span> <span class="fu">pure</span> results)</span></code></pre></div>
<p>In other words, you can implement <code>with</code> as if it were an <code>either</code> statement with one branch per value that you want to bind. However, you have to promote each value to a trivial <code>Process</code> (using <code>pure</code>) in order to combine them using <code>either</code>.</p>
<h4 id="cartesian-product-of-nfas">Cartesian product of NFAs</h4>
<p>In the introduction I noted that we can model multiple processes in PlusCal by computing the Cartesian product of their NFAs. This section explains that in more detail, first as prose and then followed by Haskell code.</p>
<p>Informally, the Cartesian product of zero or more “input” NFAs creates a composite “output” NFA where:</p>
<ul>
<li><p>The set of possible states for the output NFA is the Cartesian product of the set of possible states for each input NFA</p>
<p>In other words, if we have two input NFAs and the first NFA permits the following states:</p>
<div class="sourceCode" id="cb35"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb35-1"><a href="#cb35-1" aria-hidden="true" tabindex="-1"></a>[ <span class="dt">A</span>, <span class="dt">B</span>, <span class="dt">C</span> ]</span></code></pre></div>
<p>… and the second NFA permits the following states:</p>
<div class="sourceCode" id="cb36"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb36-1"><a href="#cb36-1" aria-hidden="true" tabindex="-1"></a>[ <span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span> ]</span></code></pre></div>
<p>… then the Cartesian product of those two sets of states is:</p>
<div class="sourceCode" id="cb37"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb37-1"><a href="#cb37-1" aria-hidden="true" tabindex="-1"></a>[ (<span class="dt">A</span>,<span class="dv">0</span>), (<span class="dt">B</span>,<span class="dv">0</span>), (<span class="dt">C</span>,<span class="dv">0</span>), (<span class="dt">A</span>,<span class="dv">1</span>), (<span class="dt">B</span>,<span class="dv">1</span>), (<span class="dt">C</span>,<span class="dv">1</span>), (<span class="dt">A</span>,<span class="dv">2</span>), (<span class="dt">B</span>,<span class="dv">2</span>), (<span class="dt">C</span>,<span class="dv">2</span>) ]</span></code></pre></div>
<p>… which is our composite set of possible states.</p></li>
<li><p>The starting state for our output NFA is the Cartesian product of the starting states for each input NFA</p>
<p>In other words, if our first NFA has a starting state of:</p>
<div class="sourceCode" id="cb38"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb38-1"><a href="#cb38-1" aria-hidden="true" tabindex="-1"></a><span class="dt">A</span></span></code></pre></div>
<p>… and our second has a starting state of:</p>
<div class="sourceCode" id="cb39"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb39-1"><a href="#cb39-1" aria-hidden="true" tabindex="-1"></a><span class="dv">0</span></span></code></pre></div>
<p>… then the Cartesian product of those two starting states is:</p>
<div class="sourceCode" id="cb40"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb40-1"><a href="#cb40-1" aria-hidden="true" tabindex="-1"></a>(<span class="dt">A</span>,<span class="dv">0</span>)</span></code></pre></div>
<p>… which is our composite starting state.</p></li>
<li><p>The set of valid transitions for any output state is the union of the set of valid transitions for the input sub-states</p>
<p>In other words, if the state <code>A</code> can transition to either state <code>B</code> or <code>C</code>:</p>
<div class="sourceCode" id="cb41"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb41-1"><a href="#cb41-1" aria-hidden="true" tabindex="-1"></a>[ <span class="dt">B</span>, <span class="dt">C</span> ]</span></code></pre></div>
<p>… and the state <code>0</code> can transition to only state <code>2</code>:</p>
<div class="sourceCode" id="cb42"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb42-1"><a href="#cb42-1" aria-hidden="true" tabindex="-1"></a>[ <span class="dv">2</span> ]</span></code></pre></div>
<p>… then the state <code>(A,0)</code> can transition to any of these states:</p>
<div class="sourceCode" id="cb43"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb43-1"><a href="#cb43-1" aria-hidden="true" tabindex="-1"></a>[ (<span class="dt">B</span>, <span class="dv">0</span>), (<span class="dt">C</span>, <span class="dv">0</span>), (<span class="dt">A</span>, <span class="dv">2</span>) ]</span></code></pre></div>
<p>… and this is because:</p>
<ul>
<li><p>If state <code>A</code> transitions to state <code>B</code>, then our composite state <code>(A,0)</code> transitions to state <code>(B,0)</code></p></li>
<li><p>If state <code>A</code> transitions to state <code>C</code>, then our composite state <code>(B,0)</code> transitions to state <code>(C,0)</code></p></li>
<li><p>If state <code>0</code> transitions to state <code>2</code>, then our composite state <code>(A,0)</code> transitions to state <code>(A,2)</code></p></li>
</ul></li>
</ul>
<h4 id="applicative-as-cartesian-product"><code>Applicative</code> as Cartesian product</h4>
<p>Haskell’s standard library defines the following <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/Control-Applicative.html"><code>Applicative</code> class</a>:</p>
<div class="sourceCode" id="cb44"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb44-1"><a href="#cb44-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">Applicative</span> f <span class="kw">where</span></span>
<span id="cb44-2"><a href="#cb44-2" aria-hidden="true" tabindex="-1"></a><span class="ot"> pure ::</span> a <span class="ot">-></span> f a</span>
<span id="cb44-3"><a href="#cb44-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb44-4"><a href="#cb44-4" aria-hidden="true" tabindex="-1"></a><span class="ot"> (<*>) ::</span> f (a <span class="ot">-></span> b) <span class="ot">-></span> f a <span class="ot">-></span> f b</span></code></pre></div>
<p>…and you can think of Haskell’s <code>Applicative</code> class as (essentially) an interface for arbitrary N-ary Cartesian products, meaning that any type that implements an <code>Applicative</code> instance gets the following family of functions for free:</p>
<div class="sourceCode" id="cb45"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb45-1"><a href="#cb45-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- The 0-ary Cartesian product</span></span>
<span id="cb45-2"><a href="#cb45-2" aria-hidden="true" tabindex="-1"></a><span class="ot">join0 ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> f ()</span>
<span id="cb45-3"><a href="#cb45-3" aria-hidden="true" tabindex="-1"></a>join0 <span class="ot">=</span> <span class="fu">pure</span> ()</span>
<span id="cb45-4"><a href="#cb45-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb45-5"><a href="#cb45-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- The unary Cartesian product</span></span>
<span id="cb45-6"><a href="#cb45-6" aria-hidden="true" tabindex="-1"></a><span class="ot">join1 ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> f a <span class="ot">-></span> f a</span>
<span id="cb45-7"><a href="#cb45-7" aria-hidden="true" tabindex="-1"></a>join1 as <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> as</span>
<span id="cb45-8"><a href="#cb45-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb45-9"><a href="#cb45-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- The binary Cartesian product</span></span>
<span id="cb45-10"><a href="#cb45-10" aria-hidden="true" tabindex="-1"></a><span class="ot">join2 ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> f a <span class="ot">-></span> f b <span class="ot">-></span> f (a, b)</span>
<span id="cb45-11"><a href="#cb45-11" aria-hidden="true" tabindex="-1"></a>join2 as bs <span class="ot">=</span> <span class="fu">pure</span> (,) <span class="op"><*></span> as <span class="op"><*></span> bs</span>
<span id="cb45-12"><a href="#cb45-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb45-13"><a href="#cb45-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- The trinary Cartesian product</span></span>
<span id="cb45-14"><a href="#cb45-14" aria-hidden="true" tabindex="-1"></a><span class="ot">join3 ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> f a <span class="ot">-></span> f b <span class="ot">-></span> f c <span class="ot">-></span> f (a, b, c)</span>
<span id="cb45-15"><a href="#cb45-15" aria-hidden="true" tabindex="-1"></a>join3 as bs cs <span class="ot">=</span> <span class="fu">pure</span> (,,) <span class="op"><*></span> as <span class="op"><*></span> bs <span class="op"><*></span> cs</span>
<span id="cb45-16"><a href="#cb45-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb45-17"><a href="#cb45-17" aria-hidden="true" tabindex="-1"></a>…</span></code></pre></div>
<p>… and so on. I deliberately implemented some of those functions in a weird way to illustrate the overall pattern.</p>
<p>This means that if we implement <code>Applicative</code> for our NFA type then we can use that interface to create arbitrary N-ary Cartesian products of NFAs.</p>
<h4 id="the-coroutine-type">The <code>Coroutine</code> type</h4>
<p>The <code>Process</code> type does implement an <code>Applicative</code> instance, but this is a different (more boring) Cartesian product and not the one we’re interested in. In fact, the <code>Process</code> type <em>cannot</em> implement the instance we’re interested in (the Cartesian product of NFAs), because our <code>Process</code> type is not a complete NFA: our <code>Process</code> type is missing a starting state.</p>
<p>This is what the following <code>Coroutine</code> type fixes by extending our <code>Process</code> type with an extra field for the starting state:</p>
<div class="sourceCode" id="cb46"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb46-1"><a href="#cb46-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Void</span> (<span class="dt">Void</span>)</span>
<span id="cb46-2"><a href="#cb46-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb46-3"><a href="#cb46-3" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Coroutine</span> label <span class="ot">=</span> <span class="dt">Begin</span> label (<span class="dt">Process</span> label <span class="dt">Void</span>)</span>
<span id="cb46-4"><a href="#cb46-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span></code></pre></div>
<p>We also constrain the <code>Process</code> inside of a <code>Coroutine</code> to return <code>Void</code> (the impossible/uninhabited type). Any <code>Process</code> that ends with no valid transitions will satisfy this type, such as a process that concludes with an <code>end</code> or <code>empty</code>:</p>
<div class="sourceCode" id="cb47"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb47-1"><a href="#cb47-1" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">Coroutine</span> <span class="dt">Int</span></span>
<span id="cb47-2"><a href="#cb47-2" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="dt">Begin</span> <span class="dv">0</span> <span class="kw">do</span></span>
<span id="cb47-3"><a href="#cb47-3" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">1</span></span>
<span id="cb47-4"><a href="#cb47-4" aria-hidden="true" tabindex="-1"></a> yield <span class="dv">2</span></span>
<span id="cb47-5"><a href="#cb47-5" aria-hidden="true" tabindex="-1"></a> end</span></code></pre></div>
<p>Once we add in the starting state we can implement <code>Applicative</code> for our <code>Coroutine</code> type, which is essentially the same thing as implementing the Cartesian product of NFAs:</p>
<div class="sourceCode" id="cb48"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb48-1"><a href="#cb48-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> <span class="dt">Coroutine</span> <span class="kw">where</span></span>
<span id="cb48-2"><a href="#cb48-2" aria-hidden="true" tabindex="-1"></a> <span class="co">-- The empty (0-ary) Cartesian product has only a single valid state, which</span></span>
<span id="cb48-3"><a href="#cb48-3" aria-hidden="true" tabindex="-1"></a> <span class="co">-- is also the starting state, and no possible transitions.</span></span>
<span id="cb48-4"><a href="#cb48-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> label <span class="ot">=</span> <span class="dt">Begin</span> label empty</span>
<span id="cb48-5"><a href="#cb48-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb48-6"><a href="#cb48-6" aria-hidden="true" tabindex="-1"></a> <span class="co">-- The (sort of) binary Cartesian product …</span></span>
<span id="cb48-7"><a href="#cb48-7" aria-hidden="true" tabindex="-1"></a> (<span class="op"><*></span>)</span>
<span id="cb48-8"><a href="#cb48-8" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … of a first NFA …</span></span>
<span id="cb48-9"><a href="#cb48-9" aria-hidden="true" tabindex="-1"></a> f<span class="op">@</span>(<span class="dt">Begin</span> label0F (<span class="dt">Choice</span> fs))</span>
<span id="cb48-10"><a href="#cb48-10" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … and a second NFA …</span></span>
<span id="cb48-11"><a href="#cb48-11" aria-hidden="true" tabindex="-1"></a> x<span class="op">@</span>(<span class="dt">Begin</span> label0X (<span class="dt">Choice</span> xs)) <span class="ot">=</span></span>
<span id="cb48-12"><a href="#cb48-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span></span>
<span id="cb48-13"><a href="#cb48-13" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … has a starting state which is (sort of) the product of the</span></span>
<span id="cb48-14"><a href="#cb48-14" aria-hidden="true" tabindex="-1"></a> <span class="co">-- first and second starting states</span></span>
<span id="cb48-15"><a href="#cb48-15" aria-hidden="true" tabindex="-1"></a> (label0F label0X)</span>
<span id="cb48-16"><a href="#cb48-16" aria-hidden="true" tabindex="-1"></a> <span class="co">-- … and the set of valid transitions is the union of valid</span></span>
<span id="cb48-17"><a href="#cb48-17" aria-hidden="true" tabindex="-1"></a> <span class="co">-- transitions for the first and second NFAs</span></span>
<span id="cb48-18"><a href="#cb48-18" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptF fs <span class="op"><|></span> <span class="fu">fmap</span> adaptX xs))</span>
<span id="cb48-19"><a href="#cb48-19" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb48-20"><a href="#cb48-20" aria-hidden="true" tabindex="-1"></a> <span class="co">-- If the first NFA transitions, then we don't disturb the state</span></span>
<span id="cb48-21"><a href="#cb48-21" aria-hidden="true" tabindex="-1"></a> <span class="co">-- of the second NFA</span></span>
<span id="cb48-22"><a href="#cb48-22" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb48-23"><a href="#cb48-23" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Yield</span> labelF restF) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb48-24"><a href="#cb48-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb48-25"><a href="#cb48-25" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> labelF restF <span class="op"><*></span> x</span>
<span id="cb48-26"><a href="#cb48-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb48-27"><a href="#cb48-27" aria-hidden="true" tabindex="-1"></a> <span class="co">-- If the second NFA transitions, then we don't disturb the state</span></span>
<span id="cb48-28"><a href="#cb48-28" aria-hidden="true" tabindex="-1"></a> <span class="co">-- of the first NFA</span></span>
<span id="cb48-29"><a href="#cb48-29" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb48-30"><a href="#cb48-30" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb48-31"><a href="#cb48-31" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb48-32"><a href="#cb48-32" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> f <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span></code></pre></div>
<p>I use “sort of” in the comments to indicate that <code>(<*>)</code> is not actually the binary Cartesian product, but it’s spiritually similar enough.</p>
<p>Moreover, we can reassure ourselves that this <code>Applicative</code> instance is well-behaved because this instance satisfies <a href="https://en.wikibooks.org/wiki/Haskell/Applicative_functors#Applicative_functor_laws">the <code>Applicative</code> laws</a>. See <a href="#appendix-d---proof-of-the-applicative-laws">Appendix D</a> for the proof of all four laws for the above instance.</p>
<h3 id="applicativedo"><code>ApplicativeDo</code></h3>
<p>Haskell is not the only language that defines an <code>Applicative</code> abstraction. For example, the <code>Cats</code> package in the Scala ecosystem also defines <a href="https://typelevel.org/cats/typeclasses/applicative.html">an <code>Applicative</code> abstraction</a>, too.</p>
<p>However, the Haskell ecosystem has one edge over other languages (including Scala), which is language support for types that only implement <code>Applicative</code> and not <code>Monad</code>.</p>
<p>Specifically, Haskell has an <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/applicative_do.html"><code>ApplicativeDo</code> extension</a>, which we can use to combine values for any <code>Applicative</code> type using <code>do</code> notation. In fact, this is why the original example using <code>pairs</code> worked, because of that extension:</p>
<div class="sourceCode" id="cb49"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb49-1"><a href="#cb49-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE ApplicativeDo #-}</span></span>
<span id="cb49-2"><a href="#cb49-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb49-3"><a href="#cb49-3" aria-hidden="true" tabindex="-1"></a><span class="ot">pairs ::</span> <span class="dt">Coroutine</span> (<span class="dt">String</span>, <span class="dt">Int</span>)</span>
<span id="cb49-4"><a href="#cb49-4" aria-hidden="true" tabindex="-1"></a>pairs <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb49-5"><a href="#cb49-5" aria-hidden="true" tabindex="-1"></a> string <span class="ot"><-</span> strings</span>
<span id="cb49-6"><a href="#cb49-6" aria-hidden="true" tabindex="-1"></a> int <span class="ot"><-</span> ints</span>
<span id="cb49-7"><a href="#cb49-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (string, int)</span></code></pre></div>
<p>When we enable that extension the Haskell compiler desugars that <code>do</code> notation to something like this:</p>
<div class="sourceCode" id="cb50"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb50-1"><a href="#cb50-1" aria-hidden="true" tabindex="-1"></a>pairs <span class="ot">=</span> f <span class="op"><$></span> strings <span class="op"><*></span> ints</span>
<span id="cb50-2"><a href="#cb50-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb50-3"><a href="#cb50-3" aria-hidden="true" tabindex="-1"></a> f string int <span class="ot">=</span> (string, int)</span></code></pre></div>
<p>… and in my opinion the version using <code>do</code> notation is more readable and ergonomic.</p>
<p>Normally <code>do</code> notation only works on types that implement <code>Monad</code>, but when we enable the <code>ApplicativeDo</code> extension then a subset of <code>do</code> notation works for types that implement <code>Applicative</code> (which is superset of the types that implement <code>Monad</code>).</p>
<p>Our <code>Coroutine</code> type is one such type that benefits from this <code>ApplicativeDo</code> extension. The <code>Coroutine</code> type can implement <code>Applicative</code>, but not <code>Monad</code>, so the only way we can use <code>do</code> notation for <code>Coroutine</code>s is if we enable <code>ApplicativeDo</code>.</p>
<p>The <code>ApplicativeDo</code> extension also plays very nicely with Haskell’s support for <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/record_puns.html"><code>NamedFieldPuns</code></a> or <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/record_wildcards.html"><code>RecordWildCards</code></a>. For example, instead of packing the labels into a tuple we could instead slurp them into a record as fields of the same name:</p>
<div class="sourceCode" id="cb51"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb51-1"><a href="#cb51-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE RecordWildCards #-}</span></span>
<span id="cb51-2"><a href="#cb51-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb51-3"><a href="#cb51-3" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Status</span> <span class="ot">=</span> <span class="dt">Status</span></span>
<span id="cb51-4"><a href="#cb51-4" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> string ::</span> <span class="dt">String</span></span>
<span id="cb51-5"><a href="#cb51-5" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> int ::</span> <span class="dt">Int</span></span>
<span id="cb51-6"><a href="#cb51-6" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb51-7"><a href="#cb51-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb51-8"><a href="#cb51-8" aria-hidden="true" tabindex="-1"></a><span class="ot">pairs ::</span> <span class="dt">Coroutine</span> <span class="dt">Status</span></span>
<span id="cb51-9"><a href="#cb51-9" aria-hidden="true" tabindex="-1"></a>pairs <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb51-10"><a href="#cb51-10" aria-hidden="true" tabindex="-1"></a> string <span class="ot"><-</span> strings</span>
<span id="cb51-11"><a href="#cb51-11" aria-hidden="true" tabindex="-1"></a> int <span class="ot"><-</span> ints</span>
<span id="cb51-12"><a href="#cb51-12" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> <span class="dt">Status</span>{<span class="op">..</span>}</span></code></pre></div>
<p>… and this scales really well to a large number of <code>Coroutine</code>s.</p>
<h4 id="conclusion">Conclusion</h4>
<p>I’ve included the complete implementation from this post in <a href="#appendix-a---complete-implementation">Appendix A</a> if you want to test this code out on your own. Once I’m done with the complete embedding of PlusCal in Haskell I’ll publish something a bit more polished.</p>
<p>If you enjoyed this post, you’ll probably also enjoy this paper:</p>
<ul>
<li><a href="https://www.staff.city.ac.uk/~ross/papers/Applicative.pdf">Functional Pearl - Applicative programming with effects</a></li>
</ul>
<p>… which was the original paper that introduced the <code>Applicative</code> abstraction.</p>
<p>I also left some “bonus commentary” in <a href="#appendix-b---free-monads">Appendix B</a> and <a href="appendix-c---monoid-semigroup">Appendix C</a> for a few digressions that didn’t quite make the cut for the main body of this post.</p>
<h4 id="appendix-a---complete-implementation">Appendix A - Complete implementation</h4>
<p>The following module only depends on the <code>pretty-show</code> package (and not even that if you delete the <code>main</code> subroutine).</p>
<div class="sourceCode" id="cb52"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb52-1"><a href="#cb52-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE ApplicativeDo #-}</span></span>
<span id="cb52-2"><a href="#cb52-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE BlockArguments #-}</span></span>
<span id="cb52-3"><a href="#cb52-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DerivingStrategies #-}</span></span>
<span id="cb52-4"><a href="#cb52-4" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE GeneralizedNewtypeDeriving #-}</span></span>
<span id="cb52-5"><a href="#cb52-5" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE UndecidableInstances #-}</span></span>
<span id="cb52-6"><a href="#cb52-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-7"><a href="#cb52-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">GHC.Exts</span> (<span class="dt">IsList</span>(..))</span>
<span id="cb52-8"><a href="#cb52-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Applicative</span> (<span class="dt">Alternative</span>(..), liftA2)</span>
<span id="cb52-9"><a href="#cb52-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Void</span> (<span class="dt">Void</span>)</span>
<span id="cb52-10"><a href="#cb52-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span> <span class="kw">hiding</span> (either)</span>
<span id="cb52-11"><a href="#cb52-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-12"><a href="#cb52-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Control.Applicative</span> <span class="kw">as</span> <span class="dt">Applicative</span></span>
<span id="cb52-13"><a href="#cb52-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Control.Monad</span> <span class="kw">as</span> <span class="dt">Monad</span></span>
<span id="cb52-14"><a href="#cb52-14" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Foldable</span> <span class="kw">as</span> <span class="dt">Foldable</span></span>
<span id="cb52-15"><a href="#cb52-15" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Text.Show.Pretty</span> <span class="kw">as</span> <span class="dt">Pretty</span></span>
<span id="cb52-16"><a href="#cb52-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-17"><a href="#cb52-17" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Process</span> label result <span class="ot">=</span> <span class="dt">Choice</span> [<span class="dt">Step</span> label result]</span>
<span id="cb52-18"><a href="#cb52-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> <span class="kw">newtype</span> (<span class="dt">IsList</span>, <span class="dt">Show</span>)</span>
<span id="cb52-19"><a href="#cb52-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-20"><a href="#cb52-20" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Step</span> label result <span class="ot">=</span> <span class="dt">Yield</span> label (<span class="dt">Process</span> label result) <span class="op">|</span> <span class="dt">Done</span> result</span>
<span id="cb52-21"><a href="#cb52-21" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span>
<span id="cb52-22"><a href="#cb52-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-23"><a href="#cb52-23" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Functor</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb52-24"><a href="#cb52-24" aria-hidden="true" tabindex="-1"></a> <span class="fu">fmap</span> <span class="ot">=</span> <span class="dt">Monad</span><span class="op">.</span>liftM</span>
<span id="cb52-25"><a href="#cb52-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-26"><a href="#cb52-26" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb52-27"><a href="#cb52-27" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> result <span class="ot">=</span> <span class="dt">Choice</span> (<span class="fu">pure</span> (<span class="dt">Done</span> result))</span>
<span id="cb52-28"><a href="#cb52-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-29"><a href="#cb52-29" aria-hidden="true" tabindex="-1"></a> (<span class="op"><*></span>) <span class="ot">=</span> <span class="dt">Monad</span><span class="op">.</span>ap</span>
<span id="cb52-30"><a href="#cb52-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-31"><a href="#cb52-31" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monad</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb52-32"><a href="#cb52-32" aria-hidden="true" tabindex="-1"></a> <span class="dt">Choice</span> ps <span class="op">>>=</span> f <span class="ot">=</span> <span class="dt">Choice</span> <span class="kw">do</span></span>
<span id="cb52-33"><a href="#cb52-33" aria-hidden="true" tabindex="-1"></a> p <span class="ot"><-</span> ps</span>
<span id="cb52-34"><a href="#cb52-34" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> p <span class="kw">of</span></span>
<span id="cb52-35"><a href="#cb52-35" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> label rest <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb52-36"><a href="#cb52-36" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (<span class="dt">Yield</span> label (rest <span class="op">>>=</span> f))</span>
<span id="cb52-37"><a href="#cb52-37" aria-hidden="true" tabindex="-1"></a> <span class="dt">Done</span> result <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb52-38"><a href="#cb52-38" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> <span class="dt">Choice</span> possibilities <span class="ot">=</span> f result</span>
<span id="cb52-39"><a href="#cb52-39" aria-hidden="true" tabindex="-1"></a> possibilities</span>
<span id="cb52-40"><a href="#cb52-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-41"><a href="#cb52-41" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Semigroup</span> result <span class="ot">=></span> <span class="dt">Semigroup</span> (<span class="dt">Process</span> label result) <span class="kw">where</span></span>
<span id="cb52-42"><a href="#cb52-42" aria-hidden="true" tabindex="-1"></a> (<span class="op"><></span>) <span class="ot">=</span> liftA2 (<span class="op"><></span>)</span>
<span id="cb52-43"><a href="#cb52-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-44"><a href="#cb52-44" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> result <span class="ot">=></span> <span class="dt">Monoid</span> (<span class="dt">Process</span> label result) <span class="kw">where</span></span>
<span id="cb52-45"><a href="#cb52-45" aria-hidden="true" tabindex="-1"></a> <span class="fu">mempty</span> <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">mempty</span></span>
<span id="cb52-46"><a href="#cb52-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-47"><a href="#cb52-47" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Alternative</span> (<span class="dt">Process</span> label) <span class="kw">where</span></span>
<span id="cb52-48"><a href="#cb52-48" aria-hidden="true" tabindex="-1"></a> empty <span class="ot">=</span> <span class="dt">Choice</span> empty</span>
<span id="cb52-49"><a href="#cb52-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-50"><a href="#cb52-50" aria-hidden="true" tabindex="-1"></a> <span class="dt">Choice</span> stepsL <span class="op"><|></span> <span class="dt">Choice</span> stepsR <span class="ot">=</span> <span class="dt">Choice</span> (stepsL <span class="op"><|></span> stepsR)</span>
<span id="cb52-51"><a href="#cb52-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-52"><a href="#cb52-52" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Coroutine</span> label <span class="ot">=</span> <span class="dt">Begin</span> label (<span class="dt">Process</span> label <span class="dt">Void</span>)</span>
<span id="cb52-53"><a href="#cb52-53" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> stock (<span class="dt">Show</span>)</span>
<span id="cb52-54"><a href="#cb52-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-55"><a href="#cb52-55" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Functor</span> <span class="dt">Coroutine</span> <span class="kw">where</span></span>
<span id="cb52-56"><a href="#cb52-56" aria-hidden="true" tabindex="-1"></a> <span class="fu">fmap</span> <span class="ot">=</span> <span class="dt">Applicative</span><span class="op">.</span>liftA</span>
<span id="cb52-57"><a href="#cb52-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-58"><a href="#cb52-58" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> <span class="dt">Coroutine</span> <span class="kw">where</span></span>
<span id="cb52-59"><a href="#cb52-59" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> label <span class="ot">=</span> <span class="dt">Begin</span> label empty</span>
<span id="cb52-60"><a href="#cb52-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-61"><a href="#cb52-61" aria-hidden="true" tabindex="-1"></a> (<span class="op"><*></span>)</span>
<span id="cb52-62"><a href="#cb52-62" aria-hidden="true" tabindex="-1"></a> f<span class="op">@</span>(<span class="dt">Begin</span> label0F (<span class="dt">Choice</span> fs))</span>
<span id="cb52-63"><a href="#cb52-63" aria-hidden="true" tabindex="-1"></a> x<span class="op">@</span>(<span class="dt">Begin</span> label0X (<span class="dt">Choice</span> xs)) <span class="ot">=</span></span>
<span id="cb52-64"><a href="#cb52-64" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (label0F label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptF fs <span class="op"><|></span> <span class="fu">fmap</span> adaptX xs))</span>
<span id="cb52-65"><a href="#cb52-65" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb52-66"><a href="#cb52-66" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb52-67"><a href="#cb52-67" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Yield</span> labelF restF) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb52-68"><a href="#cb52-68" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb52-69"><a href="#cb52-69" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> labelF restF <span class="op"><*></span> x</span>
<span id="cb52-70"><a href="#cb52-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-71"><a href="#cb52-71" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb52-72"><a href="#cb52-72" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb52-73"><a href="#cb52-73" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb52-74"><a href="#cb52-74" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> f <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb52-75"><a href="#cb52-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-76"><a href="#cb52-76" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Semigroup</span> label <span class="ot">=></span> <span class="dt">Semigroup</span> (<span class="dt">Coroutine</span> label) <span class="kw">where</span></span>
<span id="cb52-77"><a href="#cb52-77" aria-hidden="true" tabindex="-1"></a> (<span class="op"><></span>) <span class="ot">=</span> liftA2 (<span class="op"><></span>)</span>
<span id="cb52-78"><a href="#cb52-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-79"><a href="#cb52-79" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> label <span class="ot">=></span> <span class="dt">Monoid</span> (<span class="dt">Coroutine</span> label) <span class="kw">where</span></span>
<span id="cb52-80"><a href="#cb52-80" aria-hidden="true" tabindex="-1"></a> <span class="fu">mempty</span> <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">mempty</span></span>
<span id="cb52-81"><a href="#cb52-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-82"><a href="#cb52-82" aria-hidden="true" tabindex="-1"></a><span class="ot">yield ::</span> label <span class="ot">-></span> <span class="dt">Process</span> label ()</span>
<span id="cb52-83"><a href="#cb52-83" aria-hidden="true" tabindex="-1"></a>yield label <span class="ot">=</span> <span class="dt">Choice</span> [<span class="dt">Yield</span> label (<span class="fu">pure</span> ())]</span>
<span id="cb52-84"><a href="#cb52-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-85"><a href="#cb52-85" aria-hidden="true" tabindex="-1"></a><span class="fu">either</span><span class="ot"> ::</span> [<span class="dt">Process</span> label result] <span class="ot">-></span> <span class="dt">Process</span> label result</span>
<span id="cb52-86"><a href="#cb52-86" aria-hidden="true" tabindex="-1"></a><span class="fu">either</span> <span class="ot">=</span> <span class="dt">Foldable</span><span class="op">.</span>asum</span>
<span id="cb52-87"><a href="#cb52-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-88"><a href="#cb52-88" aria-hidden="true" tabindex="-1"></a><span class="ot">await ::</span> <span class="dt">Bool</span> <span class="ot">-></span> <span class="dt">Process</span> label ()</span>
<span id="cb52-89"><a href="#cb52-89" aria-hidden="true" tabindex="-1"></a>await <span class="ot">=</span> <span class="dt">Monad</span><span class="op">.</span>guard</span>
<span id="cb52-90"><a href="#cb52-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-91"><a href="#cb52-91" aria-hidden="true" tabindex="-1"></a><span class="ot">with ::</span> [result] <span class="ot">-></span> <span class="dt">Process</span> label result</span>
<span id="cb52-92"><a href="#cb52-92" aria-hidden="true" tabindex="-1"></a>with results <span class="ot">=</span> <span class="fu">either</span> (<span class="fu">map</span> <span class="fu">pure</span> results)</span>
<span id="cb52-93"><a href="#cb52-93" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-94"><a href="#cb52-94" aria-hidden="true" tabindex="-1"></a><span class="ot">end ::</span> <span class="dt">Process</span> label a</span>
<span id="cb52-95"><a href="#cb52-95" aria-hidden="true" tabindex="-1"></a>end <span class="ot">=</span> empty</span>
<span id="cb52-96"><a href="#cb52-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-97"><a href="#cb52-97" aria-hidden="true" tabindex="-1"></a><span class="ot">skip ::</span> <span class="dt">Process</span> label ()</span>
<span id="cb52-98"><a href="#cb52-98" aria-hidden="true" tabindex="-1"></a>skip <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb52-99"><a href="#cb52-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-100"><a href="#cb52-100" aria-hidden="true" tabindex="-1"></a><span class="ot">strings ::</span> <span class="dt">Coroutine</span> <span class="dt">String</span></span>
<span id="cb52-101"><a href="#cb52-101" aria-hidden="true" tabindex="-1"></a>strings <span class="ot">=</span> <span class="dt">Begin</span> <span class="st">"A"</span> <span class="kw">do</span></span>
<span id="cb52-102"><a href="#cb52-102" aria-hidden="true" tabindex="-1"></a> yield <span class="st">"B"</span></span>
<span id="cb52-103"><a href="#cb52-103" aria-hidden="true" tabindex="-1"></a> <span class="fu">either</span></span>
<span id="cb52-104"><a href="#cb52-104" aria-hidden="true" tabindex="-1"></a> [ yield <span class="st">"C"</span></span>
<span id="cb52-105"><a href="#cb52-105" aria-hidden="true" tabindex="-1"></a> , skip</span>
<span id="cb52-106"><a href="#cb52-106" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb52-107"><a href="#cb52-107" aria-hidden="true" tabindex="-1"></a> yield <span class="st">"D"</span></span>
<span id="cb52-108"><a href="#cb52-108" aria-hidden="true" tabindex="-1"></a> end</span>
<span id="cb52-109"><a href="#cb52-109" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-110"><a href="#cb52-110" aria-hidden="true" tabindex="-1"></a><span class="ot">ints ::</span> <span class="dt">Coroutine</span> <span class="dt">Int</span></span>
<span id="cb52-111"><a href="#cb52-111" aria-hidden="true" tabindex="-1"></a>ints <span class="ot">=</span> <span class="dt">Begin</span> <span class="dv">0</span> <span class="kw">do</span></span>
<span id="cb52-112"><a href="#cb52-112" aria-hidden="true" tabindex="-1"></a> x <span class="ot"><-</span> with [ <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span> ]</span>
<span id="cb52-113"><a href="#cb52-113" aria-hidden="true" tabindex="-1"></a> await (<span class="fu">even</span> x)</span>
<span id="cb52-114"><a href="#cb52-114" aria-hidden="true" tabindex="-1"></a> yield x</span>
<span id="cb52-115"><a href="#cb52-115" aria-hidden="true" tabindex="-1"></a> end</span>
<span id="cb52-116"><a href="#cb52-116" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-117"><a href="#cb52-117" aria-hidden="true" tabindex="-1"></a><span class="ot">pairs ::</span> <span class="dt">Coroutine</span> (<span class="dt">String</span>, <span class="dt">Int</span>)</span>
<span id="cb52-118"><a href="#cb52-118" aria-hidden="true" tabindex="-1"></a>pairs <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb52-119"><a href="#cb52-119" aria-hidden="true" tabindex="-1"></a> string <span class="ot"><-</span> strings</span>
<span id="cb52-120"><a href="#cb52-120" aria-hidden="true" tabindex="-1"></a> int <span class="ot"><-</span> ints</span>
<span id="cb52-121"><a href="#cb52-121" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (string, int)</span>
<span id="cb52-122"><a href="#cb52-122" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb52-123"><a href="#cb52-123" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb52-124"><a href="#cb52-124" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb52-125"><a href="#cb52-125" aria-hidden="true" tabindex="-1"></a> Pretty.pPrint strings</span>
<span id="cb52-126"><a href="#cb52-126" aria-hidden="true" tabindex="-1"></a> Pretty.pPrint ints</span>
<span id="cb52-127"><a href="#cb52-127" aria-hidden="true" tabindex="-1"></a> Pretty.pPrint pairs</span></code></pre></div>
<p>If you run that it will print:</p>
<div class="sourceCode" id="cb53"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb53-1"><a href="#cb53-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span></span>
<span id="cb53-2"><a href="#cb53-2" aria-hidden="true" tabindex="-1"></a> <span class="st">"A"</span> [ <span class="dt">Yield</span> <span class="st">"B"</span> [ <span class="dt">Yield</span> <span class="st">"C"</span> [ <span class="dt">Yield</span> <span class="st">"D"</span> [] ] , <span class="dt">Yield</span> <span class="st">"D"</span> [] ] ]</span>
<span id="cb53-3"><a href="#cb53-3" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span> <span class="dv">0</span> [ <span class="dt">Yield</span> <span class="dv">2</span> [] ]</span>
<span id="cb53-4"><a href="#cb53-4" aria-hidden="true" tabindex="-1"></a><span class="dt">Begin</span></span>
<span id="cb53-5"><a href="#cb53-5" aria-hidden="true" tabindex="-1"></a> ( <span class="st">"A"</span> , <span class="dv">0</span> )</span>
<span id="cb53-6"><a href="#cb53-6" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span></span>
<span id="cb53-7"><a href="#cb53-7" aria-hidden="true" tabindex="-1"></a> ( <span class="st">"B"</span> , <span class="dv">0</span> )</span>
<span id="cb53-8"><a href="#cb53-8" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span></span>
<span id="cb53-9"><a href="#cb53-9" aria-hidden="true" tabindex="-1"></a> ( <span class="st">"C"</span> , <span class="dv">0</span> )</span>
<span id="cb53-10"><a href="#cb53-10" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">0</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb53-11"><a href="#cb53-11" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">2</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb53-12"><a href="#cb53-12" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb53-13"><a href="#cb53-13" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">0</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb53-14"><a href="#cb53-14" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span></span>
<span id="cb53-15"><a href="#cb53-15" aria-hidden="true" tabindex="-1"></a> ( <span class="st">"B"</span> , <span class="dv">2</span> )</span>
<span id="cb53-16"><a href="#cb53-16" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">2</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb53-17"><a href="#cb53-17" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) []</span>
<span id="cb53-18"><a href="#cb53-18" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb53-19"><a href="#cb53-19" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb53-20"><a href="#cb53-20" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span></span>
<span id="cb53-21"><a href="#cb53-21" aria-hidden="true" tabindex="-1"></a> ( <span class="st">"A"</span> , <span class="dv">2</span> )</span>
<span id="cb53-22"><a href="#cb53-22" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span></span>
<span id="cb53-23"><a href="#cb53-23" aria-hidden="true" tabindex="-1"></a> ( <span class="st">"B"</span> , <span class="dv">2</span> )</span>
<span id="cb53-24"><a href="#cb53-24" aria-hidden="true" tabindex="-1"></a> [ <span class="dt">Yield</span> ( <span class="st">"C"</span> , <span class="dv">2</span> ) [ <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) [] ]</span>
<span id="cb53-25"><a href="#cb53-25" aria-hidden="true" tabindex="-1"></a> , <span class="dt">Yield</span> ( <span class="st">"D"</span> , <span class="dv">2</span> ) []</span>
<span id="cb53-26"><a href="#cb53-26" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb53-27"><a href="#cb53-27" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb53-28"><a href="#cb53-28" aria-hidden="true" tabindex="-1"></a> ]</span></code></pre></div>
<h4 id="appendix-b---free-monads">Appendix B - Free Monads</h4>
<p>The implementation for <code>Process</code> is actually a special case of a <a href="https://hackage.haskell.org/package/free-5.1.7/docs/Control-Monad-Trans-Free.html">free monad transformer</a>, except that I’ve hand-written the types and instances so that the types are easier to inspect. However, if we really wanted to code golf all of this we could have replaced all of that code with just these three lines:</p>
<div class="sourceCode" id="cb54"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb54-1"><a href="#cb54-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Trans.Free</span> (<span class="dt">FreeT</span>, liftF)</span>
<span id="cb54-2"><a href="#cb54-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb54-3"><a href="#cb54-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Process</span> label <span class="ot">=</span> <span class="dt">FreeT</span> ((,) label) []</span>
<span id="cb54-4"><a href="#cb54-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb54-5"><a href="#cb54-5" aria-hidden="true" tabindex="-1"></a>yield label <span class="ot">=</span> liftF (label, ())</span></code></pre></div>
<p>… and that would have behaved the exact same (including the same <code>Monad</code> and <code>Alternative</code> instances). You can read that as essentially saying: “A <code>Process</code> is a subroutine that alternates between emitting a <code>label</code> (i.e. <code>(,) label</code>) and branching non-deterministically (i.e. <code>[]</code>)”.</p>
<h4 id="appendix-c---monoid-semigroup">Appendix C - <code>Monoid</code> / <code>Semigroup</code></h4>
<p>If we want to be extra clever, we can implement <code>Semigroup</code> and <code>Monoid</code> instances for <code>Process</code> as suggested in this post:</p>
<ul>
<li><a href="https://www.haskellforall.com/2022/03/applicatives-should-usually-implement.html"><code>Applicative</code>s should usually implement <code>Semigroup</code> and <code>Monoid</code></a></li>
</ul>
<p>… which we would do like this:</p>
<div class="sourceCode" id="cb55"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb55-1"><a href="#cb55-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Applicative</span> (liftA2)</span>
<span id="cb55-2"><a href="#cb55-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb55-3"><a href="#cb55-3" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Semigroup</span> result <span class="ot">=></span> <span class="dt">Semigroup</span> (<span class="dt">Process</span> label result) <span class="kw">where</span></span>
<span id="cb55-4"><a href="#cb55-4" aria-hidden="true" tabindex="-1"></a> (<span class="op"><></span>) <span class="ot">=</span> liftA2 (<span class="op"><></span>)</span>
<span id="cb55-5"><a href="#cb55-5" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb55-6"><a href="#cb55-6" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> result <span class="ot">=></span> <span class="dt">Monoid</span> (<span class="dt">Process</span> label result) <span class="kw">where</span></span>
<span id="cb55-7"><a href="#cb55-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">mempty</span> <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">mempty</span></span></code></pre></div>
<p>… and then we can simplify <code>skip</code> a tiny bit further to:</p>
<div class="sourceCode" id="cb56"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb56-1"><a href="#cb56-1" aria-hidden="true" tabindex="-1"></a>skip <span class="ot">=</span> <span class="fu">mempty</span></span></code></pre></div>
<h4 id="appendix-d---proof-of-the-applicative-laws">Appendix D - Proof of the <code>Applicative</code> laws</h4>
<p>The first <code>Applicative</code> law requires that:</p>
<div class="sourceCode" id="cb57"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb57-1"><a href="#cb57-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> v <span class="ot">=</span> v</span></code></pre></div>
<p>Proof:</p>
<div class="sourceCode" id="cb58"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb58-1"><a href="#cb58-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> v</span>
<span id="cb58-2"><a href="#cb58-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-3"><a href="#cb58-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`:</span></span>
<span id="cb58-4"><a href="#cb58-4" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb58-5"><a href="#cb58-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb58-6"><a href="#cb58-6" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> <span class="fu">id</span> empty <span class="op"><*></span> v</span>
<span id="cb58-7"><a href="#cb58-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-8"><a href="#cb58-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- Define:</span></span>
<span id="cb58-9"><a href="#cb58-9" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb58-10"><a href="#cb58-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- v = Begin label0X (Choice xs)</span></span>
<span id="cb58-11"><a href="#cb58-11" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> <span class="fu">id</span> empty <span class="op"><*></span> <span class="dt">Begin</span> label0X (<span class="dt">Choice</span> xs)</span>
<span id="cb58-12"><a href="#cb58-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-13"><a href="#cb58-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb58-14"><a href="#cb58-14" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptF empty <span class="op"><|></span> <span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-15"><a href="#cb58-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-16"><a href="#cb58-16" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-17"><a href="#cb58-17" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Yield</span> labelF restF) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb58-18"><a href="#cb58-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-19"><a href="#cb58-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> labelF restF <span class="op"><*></span> <span class="dt">Begin</span> label0X (<span class="dt">Choice</span> xs)</span>
<span id="cb58-20"><a href="#cb58-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-21"><a href="#cb58-21" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-22"><a href="#cb58-22" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb58-23"><a href="#cb58-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-24"><a href="#cb58-24" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> <span class="fu">id</span> empty <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb58-25"><a href="#cb58-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-26"><a href="#cb58-26" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f empty = empty</span></span>
<span id="cb58-27"><a href="#cb58-27" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (empty <span class="op"><|></span> <span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-28"><a href="#cb58-28" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-29"><a href="#cb58-29" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-30"><a href="#cb58-30" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb58-31"><a href="#cb58-31" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-32"><a href="#cb58-32" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> <span class="fu">id</span> empty <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb58-33"><a href="#cb58-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-34"><a href="#cb58-34" aria-hidden="true" tabindex="-1"></a><span class="co">-- empty <|> xs = xs</span></span>
<span id="cb58-35"><a href="#cb58-35" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-36"><a href="#cb58-36" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-37"><a href="#cb58-37" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-38"><a href="#cb58-38" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb58-39"><a href="#cb58-39" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-40"><a href="#cb58-40" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> <span class="fu">id</span> empty <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb58-41"><a href="#cb58-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-42"><a href="#cb58-42" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`, in reverse</span></span>
<span id="cb58-43"><a href="#cb58-43" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb58-44"><a href="#cb58-44" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb58-45"><a href="#cb58-45" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-46"><a href="#cb58-46" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-47"><a href="#cb58-47" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-48"><a href="#cb58-48" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb58-49"><a href="#cb58-49" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-50"><a href="#cb58-50" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">id</span> <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb58-51"><a href="#cb58-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-52"><a href="#cb58-52" aria-hidden="true" tabindex="-1"></a><span class="co">-- Induction: pure id <*> v = v</span></span>
<span id="cb58-53"><a href="#cb58-53" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-54"><a href="#cb58-54" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-55"><a href="#cb58-55" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-56"><a href="#cb58-56" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb58-57"><a href="#cb58-57" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-58"><a href="#cb58-58" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb58-59"><a href="#cb58-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-60"><a href="#cb58-60" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify</span></span>
<span id="cb58-61"><a href="#cb58-61" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-62"><a href="#cb58-62" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-63"><a href="#cb58-63" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb58-64"><a href="#cb58-64" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelX restX</span>
<span id="cb58-65"><a href="#cb58-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-66"><a href="#cb58-66" aria-hidden="true" tabindex="-1"></a><span class="co">-- Simplify</span></span>
<span id="cb58-67"><a href="#cb58-67" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptX xs))</span>
<span id="cb58-68"><a href="#cb58-68" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb58-69"><a href="#cb58-69" aria-hidden="true" tabindex="-1"></a> adaptX <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb58-70"><a href="#cb58-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-71"><a href="#cb58-71" aria-hidden="true" tabindex="-1"></a><span class="co">-- Functor identity law:</span></span>
<span id="cb58-72"><a href="#cb58-72" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb58-73"><a href="#cb58-73" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap id = id</span></span>
<span id="cb58-74"><a href="#cb58-74" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="fu">id</span> label0X) (<span class="dt">Choice</span> (<span class="fu">id</span> xs))</span>
<span id="cb58-75"><a href="#cb58-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-76"><a href="#cb58-76" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `id`:</span></span>
<span id="cb58-77"><a href="#cb58-77" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb58-78"><a href="#cb58-78" aria-hidden="true" tabindex="-1"></a><span class="co">-- id x = x</span></span>
<span id="cb58-79"><a href="#cb58-79" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> label0X (<span class="dt">Choice</span> xs)</span>
<span id="cb58-80"><a href="#cb58-80" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb58-81"><a href="#cb58-81" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `v`, in reverse</span></span>
<span id="cb58-82"><a href="#cb58-82" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb58-83"><a href="#cb58-83" aria-hidden="true" tabindex="-1"></a><span class="co">-- v = Begin label0X (Choice xs)</span></span>
<span id="cb58-84"><a href="#cb58-84" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> v</span></code></pre></div>
<p>The second <code>Applicative</code> law requires that:</p>
<div class="sourceCode" id="cb59"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb59-1"><a href="#cb59-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> (<span class="op">.</span>) <span class="op"><*></span> u <span class="op"><*></span> v <span class="op"><*></span> w <span class="ot">=</span> u <span class="op"><*></span> (v <span class="op"><*></span> w)</span></code></pre></div>
<p>Proof:</p>
<div class="sourceCode" id="cb60"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb60-1"><a href="#cb60-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> (<span class="op">.</span>) <span class="op"><*></span> u <span class="op"><*></span> v <span class="op"><*></span> w</span>
<span id="cb60-2"><a href="#cb60-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-3"><a href="#cb60-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Define:</span></span>
<span id="cb60-4"><a href="#cb60-4" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb60-5"><a href="#cb60-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- u = Begin label0U (Choice us)</span></span>
<span id="cb60-6"><a href="#cb60-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- v = Begin label0V (Choice vs)</span></span>
<span id="cb60-7"><a href="#cb60-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- w = Begin label0W (Choice ws)</span></span>
<span id="cb60-8"><a href="#cb60-8" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (<span class="op">.</span>)</span>
<span id="cb60-9"><a href="#cb60-9" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-10"><a href="#cb60-10" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-11"><a href="#cb60-11" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-12"><a href="#cb60-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-13"><a href="#cb60-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`:</span></span>
<span id="cb60-14"><a href="#cb60-14" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-15"><a href="#cb60-15" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-16"><a href="#cb60-16" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-17"><a href="#cb60-17" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-18"><a href="#cb60-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-19"><a href="#cb60-19" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb60-20"><a href="#cb60-20" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> ((<span class="op">.</span>) label0U) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptF empty <span class="op"><|></span> <span class="fu">fmap</span> adaptU us))</span>
<span id="cb60-21"><a href="#cb60-21" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-22"><a href="#cb60-22" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-23"><a href="#cb60-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-24"><a href="#cb60-24" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-25"><a href="#cb60-25" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Yield</span> labelF restF) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb60-26"><a href="#cb60-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-27"><a href="#cb60-27" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> labelF restF <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-28"><a href="#cb60-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-29"><a href="#cb60-29" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-30"><a href="#cb60-30" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFU restFU</span>
<span id="cb60-31"><a href="#cb60-31" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-32"><a href="#cb60-32" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">.</span>) empty <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-33"><a href="#cb60-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-34"><a href="#cb60-34" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f empty = empty</span></span>
<span id="cb60-35"><a href="#cb60-35" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> ((<span class="op">.</span>) label0U) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us))</span>
<span id="cb60-36"><a href="#cb60-36" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-37"><a href="#cb60-37" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-38"><a href="#cb60-38" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-39"><a href="#cb60-39" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-40"><a href="#cb60-40" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFU restFU</span>
<span id="cb60-41"><a href="#cb60-41" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-42"><a href="#cb60-42" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">.</span>) empty <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-43"><a href="#cb60-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-44"><a href="#cb60-44" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb60-45"><a href="#cb60-45" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U <span class="op">.</span> label0V)</span>
<span id="cb60-46"><a href="#cb60-46" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptFU (<span class="fu">fmap</span> adaptU us) <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs))</span>
<span id="cb60-47"><a href="#cb60-47" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-48"><a href="#cb60-48" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-49"><a href="#cb60-49" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-50"><a href="#cb60-50" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFU restFU</span>
<span id="cb60-51"><a href="#cb60-51" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-52"><a href="#cb60-52" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">.</span>) empty <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-53"><a href="#cb60-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-54"><a href="#cb60-54" aria-hidden="true" tabindex="-1"></a> adaptFU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-55"><a href="#cb60-55" aria-hidden="true" tabindex="-1"></a> adaptFU (<span class="dt">Yield</span> labelFU restFU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-56"><a href="#cb60-56" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-57"><a href="#cb60-57" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-58"><a href="#cb60-58" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-59"><a href="#cb60-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-60"><a href="#cb60-60" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-61"><a href="#cb60-61" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-62"><a href="#cb60-62" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-63"><a href="#cb60-63" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-64"><a href="#cb60-64" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> ((<span class="op">.</span>) label0U) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us))</span>
<span id="cb60-65"><a href="#cb60-65" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-66"><a href="#cb60-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-67"><a href="#cb60-67" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`, in reverse</span></span>
<span id="cb60-68"><a href="#cb60-68" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U <span class="op">.</span> label0V)</span>
<span id="cb60-69"><a href="#cb60-69" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptFU (<span class="fu">fmap</span> adaptU us) <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs))</span>
<span id="cb60-70"><a href="#cb60-70" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-71"><a href="#cb60-71" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-72"><a href="#cb60-72" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-73"><a href="#cb60-73" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFU restFU</span>
<span id="cb60-74"><a href="#cb60-74" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-75"><a href="#cb60-75" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">.</span>) empty <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-76"><a href="#cb60-76" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-77"><a href="#cb60-77" aria-hidden="true" tabindex="-1"></a> adaptFU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-78"><a href="#cb60-78" aria-hidden="true" tabindex="-1"></a> adaptFU (<span class="dt">Yield</span> labelFU restFU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-79"><a href="#cb60-79" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-80"><a href="#cb60-80" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-81"><a href="#cb60-81" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-82"><a href="#cb60-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-83"><a href="#cb60-83" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-84"><a href="#cb60-84" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-85"><a href="#cb60-85" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-86"><a href="#cb60-86" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-87"><a href="#cb60-87" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-88"><a href="#cb60-88" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-89"><a href="#cb60-89" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-90"><a href="#cb60-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-91"><a href="#cb60-91" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) x</span></span>
<span id="cb60-92"><a href="#cb60-92" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U <span class="op">.</span> label0V)</span>
<span id="cb60-93"><a href="#cb60-93" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> (adaptFU <span class="op">.</span> adaptU) us <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs))</span>
<span id="cb60-94"><a href="#cb60-94" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-95"><a href="#cb60-95" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-96"><a href="#cb60-96" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-97"><a href="#cb60-97" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFU restFU</span>
<span id="cb60-98"><a href="#cb60-98" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-99"><a href="#cb60-99" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">.</span>) empty <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-100"><a href="#cb60-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-101"><a href="#cb60-101" aria-hidden="true" tabindex="-1"></a> adaptFU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-102"><a href="#cb60-102" aria-hidden="true" tabindex="-1"></a> adaptFU (<span class="dt">Yield</span> labelFU restFU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-103"><a href="#cb60-103" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-104"><a href="#cb60-104" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-105"><a href="#cb60-105" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFU restFU <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-106"><a href="#cb60-106" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-107"><a href="#cb60-107" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-108"><a href="#cb60-108" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-109"><a href="#cb60-109" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-110"><a href="#cb60-110" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-111"><a href="#cb60-111" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-112"><a href="#cb60-112" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-113"><a href="#cb60-113" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-114"><a href="#cb60-114" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-115"><a href="#cb60-115" aria-hidden="true" tabindex="-1"></a><span class="co">-- Consolidate (adaptFU . adaptU)</span></span>
<span id="cb60-116"><a href="#cb60-116" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U <span class="op">.</span> label0V) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs))</span>
<span id="cb60-117"><a href="#cb60-117" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-118"><a href="#cb60-118" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-119"><a href="#cb60-119" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-120"><a href="#cb60-120" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-121"><a href="#cb60-121" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-122"><a href="#cb60-122" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-123"><a href="#cb60-123" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-124"><a href="#cb60-124" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-125"><a href="#cb60-125" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-126"><a href="#cb60-126" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-127"><a href="#cb60-127" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-128"><a href="#cb60-128" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-129"><a href="#cb60-129" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-130"><a href="#cb60-130" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-131"><a href="#cb60-131" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-132"><a href="#cb60-132" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-133"><a href="#cb60-133" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-134"><a href="#cb60-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-135"><a href="#cb60-135" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb60-136"><a href="#cb60-136" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-137"><a href="#cb60-137" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-138"><a href="#cb60-138" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptFUV (<span class="fu">fmap</span> adaptU us <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs)</span>
<span id="cb60-139"><a href="#cb60-139" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-140"><a href="#cb60-140" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-141"><a href="#cb60-141" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-142"><a href="#cb60-142" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-143"><a href="#cb60-143" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-144"><a href="#cb60-144" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-145"><a href="#cb60-145" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-146"><a href="#cb60-146" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-147"><a href="#cb60-147" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-148"><a href="#cb60-148" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-149"><a href="#cb60-149" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-150"><a href="#cb60-150" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-151"><a href="#cb60-151" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-152"><a href="#cb60-152" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-153"><a href="#cb60-153" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-154"><a href="#cb60-154" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-155"><a href="#cb60-155" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-156"><a href="#cb60-156" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-157"><a href="#cb60-157" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-158"><a href="#cb60-158" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-159"><a href="#cb60-159" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-160"><a href="#cb60-160" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Yield</span> labelFUV restFUV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-161"><a href="#cb60-161" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-162"><a href="#cb60-162" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-163"><a href="#cb60-163" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV</span>
<span id="cb60-164"><a href="#cb60-164" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-165"><a href="#cb60-165" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-166"><a href="#cb60-166" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-167"><a href="#cb60-167" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-168"><a href="#cb60-168" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-169"><a href="#cb60-169" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-170"><a href="#cb60-170" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (label0U <span class="op">.</span> label0V)</span>
<span id="cb60-171"><a href="#cb60-171" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs))</span>
<span id="cb60-172"><a href="#cb60-172" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-173"><a href="#cb60-173" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-174"><a href="#cb60-174" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`, in reverse</span></span>
<span id="cb60-175"><a href="#cb60-175" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-176"><a href="#cb60-176" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-177"><a href="#cb60-177" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptFUV (<span class="fu">fmap</span> adaptU us <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs)</span>
<span id="cb60-178"><a href="#cb60-178" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-179"><a href="#cb60-179" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-180"><a href="#cb60-180" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-181"><a href="#cb60-181" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-182"><a href="#cb60-182" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-183"><a href="#cb60-183" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-184"><a href="#cb60-184" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-185"><a href="#cb60-185" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-186"><a href="#cb60-186" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-187"><a href="#cb60-187" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-188"><a href="#cb60-188" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-189"><a href="#cb60-189" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-190"><a href="#cb60-190" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-191"><a href="#cb60-191" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-192"><a href="#cb60-192" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-193"><a href="#cb60-193" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-194"><a href="#cb60-194" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-195"><a href="#cb60-195" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-196"><a href="#cb60-196" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-197"><a href="#cb60-197" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-198"><a href="#cb60-198" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-199"><a href="#cb60-199" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Yield</span> labelFUV restFUV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-200"><a href="#cb60-200" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-201"><a href="#cb60-201" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-202"><a href="#cb60-202" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV</span>
<span id="cb60-203"><a href="#cb60-203" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-204"><a href="#cb60-204" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-205"><a href="#cb60-205" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-206"><a href="#cb60-206" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-207"><a href="#cb60-207" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-208"><a href="#cb60-208" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-209"><a href="#cb60-209" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-210"><a href="#cb60-210" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-211"><a href="#cb60-211" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-212"><a href="#cb60-212" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-213"><a href="#cb60-213" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-214"><a href="#cb60-214" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (x <|> y) = fmap f x <|> fmap f y</span></span>
<span id="cb60-215"><a href="#cb60-215" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-216"><a href="#cb60-216" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-217"><a href="#cb60-217" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptFUV (<span class="fu">fmap</span> adaptU us)</span>
<span id="cb60-218"><a href="#cb60-218" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptFUV (<span class="fu">fmap</span> adaptV vs)</span>
<span id="cb60-219"><a href="#cb60-219" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-220"><a href="#cb60-220" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-221"><a href="#cb60-221" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-222"><a href="#cb60-222" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-223"><a href="#cb60-223" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-224"><a href="#cb60-224" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-225"><a href="#cb60-225" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-226"><a href="#cb60-226" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-227"><a href="#cb60-227" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-228"><a href="#cb60-228" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-229"><a href="#cb60-229" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-230"><a href="#cb60-230" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-231"><a href="#cb60-231" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-232"><a href="#cb60-232" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-233"><a href="#cb60-233" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-234"><a href="#cb60-234" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-235"><a href="#cb60-235" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-236"><a href="#cb60-236" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-237"><a href="#cb60-237" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-238"><a href="#cb60-238" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-239"><a href="#cb60-239" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-240"><a href="#cb60-240" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Yield</span> labelFUV restFUV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-241"><a href="#cb60-241" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-242"><a href="#cb60-242" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-243"><a href="#cb60-243" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV</span>
<span id="cb60-244"><a href="#cb60-244" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-245"><a href="#cb60-245" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-246"><a href="#cb60-246" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-247"><a href="#cb60-247" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-248"><a href="#cb60-248" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-249"><a href="#cb60-249" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-250"><a href="#cb60-250" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-251"><a href="#cb60-251" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-252"><a href="#cb60-252" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-253"><a href="#cb60-253" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-254"><a href="#cb60-254" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-255"><a href="#cb60-255" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (fmap g x) = fmap (f . g) x</span></span>
<span id="cb60-256"><a href="#cb60-256" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-257"><a href="#cb60-257" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-258"><a href="#cb60-258" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> (adaptFUV <span class="op">.</span> adaptU) us</span>
<span id="cb60-259"><a href="#cb60-259" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> (adaptFUV <span class="op">.</span> adaptV) vs</span>
<span id="cb60-260"><a href="#cb60-260" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-261"><a href="#cb60-261" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-262"><a href="#cb60-262" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-263"><a href="#cb60-263" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-264"><a href="#cb60-264" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-265"><a href="#cb60-265" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-266"><a href="#cb60-266" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-267"><a href="#cb60-267" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-268"><a href="#cb60-268" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-269"><a href="#cb60-269" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-270"><a href="#cb60-270" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-271"><a href="#cb60-271" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-272"><a href="#cb60-272" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-273"><a href="#cb60-273" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-274"><a href="#cb60-274" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-275"><a href="#cb60-275" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-276"><a href="#cb60-276" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-277"><a href="#cb60-277" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-278"><a href="#cb60-278" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-279"><a href="#cb60-279" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-280"><a href="#cb60-280" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-281"><a href="#cb60-281" aria-hidden="true" tabindex="-1"></a> adaptFUV (<span class="dt">Yield</span> labelFUV restFUV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-282"><a href="#cb60-282" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-283"><a href="#cb60-283" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-284"><a href="#cb60-284" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV</span>
<span id="cb60-285"><a href="#cb60-285" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-286"><a href="#cb60-286" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-287"><a href="#cb60-287" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-288"><a href="#cb60-288" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-289"><a href="#cb60-289" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-290"><a href="#cb60-290" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-291"><a href="#cb60-291" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-292"><a href="#cb60-292" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-293"><a href="#cb60-293" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-294"><a href="#cb60-294" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-295"><a href="#cb60-295" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-296"><a href="#cb60-296" aria-hidden="true" tabindex="-1"></a><span class="co">-- Consolidate (adaptFUV . adaptU) and (adaptFUV . adaptV)</span></span>
<span id="cb60-297"><a href="#cb60-297" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-298"><a href="#cb60-298" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-299"><a href="#cb60-299" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptU us</span>
<span id="cb60-300"><a href="#cb60-300" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs</span>
<span id="cb60-301"><a href="#cb60-301" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-302"><a href="#cb60-302" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-303"><a href="#cb60-303" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-304"><a href="#cb60-304" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-305"><a href="#cb60-305" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-306"><a href="#cb60-306" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-307"><a href="#cb60-307" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-308"><a href="#cb60-308" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-309"><a href="#cb60-309" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-310"><a href="#cb60-310" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-311"><a href="#cb60-311" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-312"><a href="#cb60-312" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-313"><a href="#cb60-313" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-314"><a href="#cb60-314" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-315"><a href="#cb60-315" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-316"><a href="#cb60-316" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-317"><a href="#cb60-317" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-318"><a href="#cb60-318" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-319"><a href="#cb60-319" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-320"><a href="#cb60-320" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-321"><a href="#cb60-321" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-322"><a href="#cb60-322" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-323"><a href="#cb60-323" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-324"><a href="#cb60-324" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-325"><a href="#cb60-325" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-326"><a href="#cb60-326" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-327"><a href="#cb60-327" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> (<span class="op">.</span>) empty</span>
<span id="cb60-328"><a href="#cb60-328" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-329"><a href="#cb60-329" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-330"><a href="#cb60-330" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-331"><a href="#cb60-331" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-332"><a href="#cb60-332" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`, in reverse</span></span>
<span id="cb60-333"><a href="#cb60-333" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb60-334"><a href="#cb60-334" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb60-335"><a href="#cb60-335" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-336"><a href="#cb60-336" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-337"><a href="#cb60-337" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptU us</span>
<span id="cb60-338"><a href="#cb60-338" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs</span>
<span id="cb60-339"><a href="#cb60-339" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-340"><a href="#cb60-340" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-341"><a href="#cb60-341" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-342"><a href="#cb60-342" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-343"><a href="#cb60-343" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-344"><a href="#cb60-344" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-345"><a href="#cb60-345" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-346"><a href="#cb60-346" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-347"><a href="#cb60-347" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> (<span class="op">.</span>)</span>
<span id="cb60-348"><a href="#cb60-348" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-349"><a href="#cb60-349" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-350"><a href="#cb60-350" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-351"><a href="#cb60-351" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-352"><a href="#cb60-352" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-353"><a href="#cb60-353" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-354"><a href="#cb60-354" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-355"><a href="#cb60-355" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-356"><a href="#cb60-356" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> (<span class="op">.</span>)</span>
<span id="cb60-357"><a href="#cb60-357" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-358"><a href="#cb60-358" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-359"><a href="#cb60-359" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-360"><a href="#cb60-360" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-361"><a href="#cb60-361" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-362"><a href="#cb60-362" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-363"><a href="#cb60-363" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-364"><a href="#cb60-364" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-365"><a href="#cb60-365" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> (<span class="op">.</span>)</span>
<span id="cb60-366"><a href="#cb60-366" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-367"><a href="#cb60-367" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-368"><a href="#cb60-368" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-369"><a href="#cb60-369" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-370"><a href="#cb60-370" aria-hidden="true" tabindex="-1"></a><span class="co">-- Induction: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)</span></span>
<span id="cb60-371"><a href="#cb60-371" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-372"><a href="#cb60-372" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-373"><a href="#cb60-373" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptU us</span>
<span id="cb60-374"><a href="#cb60-374" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptV vs</span>
<span id="cb60-375"><a href="#cb60-375" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws</span>
<span id="cb60-376"><a href="#cb60-376" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-377"><a href="#cb60-377" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-378"><a href="#cb60-378" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-379"><a href="#cb60-379" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-380"><a href="#cb60-380" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-381"><a href="#cb60-381" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-382"><a href="#cb60-382" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-383"><a href="#cb60-383" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelU restU</span>
<span id="cb60-384"><a href="#cb60-384" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-385"><a href="#cb60-385" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-386"><a href="#cb60-386" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-387"><a href="#cb60-387" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-388"><a href="#cb60-388" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-389"><a href="#cb60-389" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelFUV restFUV</span>
<span id="cb60-390"><a href="#cb60-390" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-391"><a href="#cb60-391" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUV restFUV <span class="ot">=</span></span>
<span id="cb60-392"><a href="#cb60-392" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-393"><a href="#cb60-393" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Begin</span> labelV restV</span>
<span id="cb60-394"><a href="#cb60-394" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-395"><a href="#cb60-395" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-396"><a href="#cb60-396" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-397"><a href="#cb60-397" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-398"><a href="#cb60-398" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelFUVW restFUVW</span>
<span id="cb60-399"><a href="#cb60-399" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-400"><a href="#cb60-400" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFUVW restFUVW <span class="ot">=</span></span>
<span id="cb60-401"><a href="#cb60-401" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-402"><a href="#cb60-402" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-403"><a href="#cb60-403" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-404"><a href="#cb60-404" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-405"><a href="#cb60-405" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-406"><a href="#cb60-406" aria-hidden="true" tabindex="-1"></a><span class="co">-- Unfactor up `adaptV` into `adaptVW . adaptV` and `adaptW` into</span></span>
<span id="cb60-407"><a href="#cb60-407" aria-hidden="true" tabindex="-1"></a><span class="co">-- `adaptVW . adaptW`</span></span>
<span id="cb60-408"><a href="#cb60-408" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-409"><a href="#cb60-409" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-410"><a href="#cb60-410" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptU us</span>
<span id="cb60-411"><a href="#cb60-411" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptVW (<span class="fu">fmap</span> adaptV vs)</span>
<span id="cb60-412"><a href="#cb60-412" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptVW (adaptW ws)</span>
<span id="cb60-413"><a href="#cb60-413" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-414"><a href="#cb60-414" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-415"><a href="#cb60-415" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-416"><a href="#cb60-416" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-417"><a href="#cb60-417" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUVW restUVW</span>
<span id="cb60-418"><a href="#cb60-418" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-419"><a href="#cb60-419" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelUVW restUVW <span class="ot">=</span></span>
<span id="cb60-420"><a href="#cb60-420" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelU (<span class="dt">Choice</span> us)</span>
<span id="cb60-421"><a href="#cb60-421" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-422"><a href="#cb60-422" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-423"><a href="#cb60-423" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-424"><a href="#cb60-424" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-425"><a href="#cb60-425" aria-hidden="true" tabindex="-1"></a> adaptVW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-426"><a href="#cb60-426" aria-hidden="true" tabindex="-1"></a> adaptVW (<span class="dt">Yield</span> labelVW restVW) <span class="ot">=</span> <span class="dt">Yield</span> labelUVW restUVW</span>
<span id="cb60-427"><a href="#cb60-427" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-428"><a href="#cb60-428" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelUVW restUVW <span class="ot">=</span></span>
<span id="cb60-429"><a href="#cb60-429" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-430"><a href="#cb60-430" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelVW restVW</span>
<span id="cb60-431"><a href="#cb60-431" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-432"><a href="#cb60-432" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-433"><a href="#cb60-433" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-434"><a href="#cb60-434" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-435"><a href="#cb60-435" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelVW restVW <span class="ot">=</span> <span class="dt">Begin</span> labelV restV <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-436"><a href="#cb60-436" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-437"><a href="#cb60-437" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-438"><a href="#cb60-438" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-439"><a href="#cb60-439" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-440"><a href="#cb60-440" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> lableVW restVW <span class="ot">=</span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs) <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-441"><a href="#cb60-441" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-442"><a href="#cb60-442" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f (x <|> y) = fmap f x <|> fmap f y</span></span>
<span id="cb60-443"><a href="#cb60-443" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-444"><a href="#cb60-444" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-445"><a href="#cb60-445" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptU us</span>
<span id="cb60-446"><a href="#cb60-446" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptVW (<span class="fu">fmap</span> adaptV vs <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws)</span>
<span id="cb60-447"><a href="#cb60-447" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-448"><a href="#cb60-448" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-449"><a href="#cb60-449" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-450"><a href="#cb60-450" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-451"><a href="#cb60-451" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUVW restUVW</span>
<span id="cb60-452"><a href="#cb60-452" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-453"><a href="#cb60-453" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelUVW restUVW <span class="ot">=</span></span>
<span id="cb60-454"><a href="#cb60-454" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelU (<span class="dt">Choice</span> us)</span>
<span id="cb60-455"><a href="#cb60-455" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-456"><a href="#cb60-456" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-457"><a href="#cb60-457" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-458"><a href="#cb60-458" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-459"><a href="#cb60-459" aria-hidden="true" tabindex="-1"></a> adaptVW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-460"><a href="#cb60-460" aria-hidden="true" tabindex="-1"></a> adaptVW (<span class="dt">Yield</span> labelVW restVW) <span class="ot">=</span> <span class="dt">Yield</span> labelUVW restUVW</span>
<span id="cb60-461"><a href="#cb60-461" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-462"><a href="#cb60-462" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelUVW restUVW <span class="ot">=</span></span>
<span id="cb60-463"><a href="#cb60-463" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-464"><a href="#cb60-464" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelVW restVW</span>
<span id="cb60-465"><a href="#cb60-465" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-466"><a href="#cb60-466" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-467"><a href="#cb60-467" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-468"><a href="#cb60-468" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-469"><a href="#cb60-469" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelVW restVW <span class="ot">=</span> <span class="dt">Begin</span> labelV restV <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-470"><a href="#cb60-470" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-471"><a href="#cb60-471" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-472"><a href="#cb60-472" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-473"><a href="#cb60-473" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-474"><a href="#cb60-474" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> lableVW restVW <span class="ot">=</span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs) <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-475"><a href="#cb60-475" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-476"><a href="#cb60-476" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb60-477"><a href="#cb60-477" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (label0U (label0V label0W))</span>
<span id="cb60-478"><a href="#cb60-478" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span></span>
<span id="cb60-479"><a href="#cb60-479" aria-hidden="true" tabindex="-1"></a> ( <span class="fu">fmap</span> adaptU us</span>
<span id="cb60-480"><a href="#cb60-480" aria-hidden="true" tabindex="-1"></a> <span class="op"><|></span> <span class="fu">fmap</span> adaptVW (<span class="fu">fmap</span> adaptV vs <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws)</span>
<span id="cb60-481"><a href="#cb60-481" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-482"><a href="#cb60-482" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-483"><a href="#cb60-483" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-484"><a href="#cb60-484" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-485"><a href="#cb60-485" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUVW restUVW</span>
<span id="cb60-486"><a href="#cb60-486" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-487"><a href="#cb60-487" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelUVW restUVW <span class="ot">=</span></span>
<span id="cb60-488"><a href="#cb60-488" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelU (<span class="dt">Choice</span> us)</span>
<span id="cb60-489"><a href="#cb60-489" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> (label0V label0W)</span>
<span id="cb60-490"><a href="#cb60-490" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptV vs <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws))</span>
<span id="cb60-491"><a href="#cb60-491" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-492"><a href="#cb60-492" aria-hidden="true" tabindex="-1"></a> adaptVW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-493"><a href="#cb60-493" aria-hidden="true" tabindex="-1"></a> adaptVW (<span class="dt">Yield</span> labelVW restVW) <span class="ot">=</span> <span class="dt">Yield</span> labelUVW restUVW</span>
<span id="cb60-494"><a href="#cb60-494" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-495"><a href="#cb60-495" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelUVW restUVW <span class="ot">=</span></span>
<span id="cb60-496"><a href="#cb60-496" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-497"><a href="#cb60-497" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> labelVW restVW</span>
<span id="cb60-498"><a href="#cb60-498" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-499"><a href="#cb60-499" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-500"><a href="#cb60-500" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-501"><a href="#cb60-501" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-502"><a href="#cb60-502" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelVW restVW <span class="ot">=</span> <span class="dt">Begin</span> labelV restV <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-503"><a href="#cb60-503" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-504"><a href="#cb60-504" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-505"><a href="#cb60-505" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-506"><a href="#cb60-506" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-507"><a href="#cb60-507" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> lableVW restVW <span class="ot">=</span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs) <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-508"><a href="#cb60-508" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-509"><a href="#cb60-509" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`, in reverse</span></span>
<span id="cb60-510"><a href="#cb60-510" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-511"><a href="#cb60-511" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> (label0V label0W)</span>
<span id="cb60-512"><a href="#cb60-512" aria-hidden="true" tabindex="-1"></a> (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptV vs <span class="op"><|></span> <span class="fu">fmap</span> adaptW ws))</span>
<span id="cb60-513"><a href="#cb60-513" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-514"><a href="#cb60-514" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-515"><a href="#cb60-515" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelV restV) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-516"><a href="#cb60-516" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-517"><a href="#cb60-517" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelVW restVW <span class="ot">=</span> <span class="dt">Begin</span> labelV restV <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-518"><a href="#cb60-518" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-519"><a href="#cb60-519" aria-hidden="true" tabindex="-1"></a> adaptW (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb60-520"><a href="#cb60-520" aria-hidden="true" tabindex="-1"></a> adaptV (<span class="dt">Yield</span> labelW restW) <span class="ot">=</span> <span class="dt">Yield</span> labelVW restVW</span>
<span id="cb60-521"><a href="#cb60-521" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb60-522"><a href="#cb60-522" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> lableVW restVW <span class="ot">=</span> <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs) <span class="op"><*></span> <span class="dt">Begin</span> labelW restW</span>
<span id="cb60-523"><a href="#cb60-523" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-524"><a href="#cb60-524" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`, in reverse</span></span>
<span id="cb60-525"><a href="#cb60-525" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> label0U (<span class="dt">Choice</span> us)</span>
<span id="cb60-526"><a href="#cb60-526" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> ( <span class="dt">Begin</span> label0V (<span class="dt">Choice</span> vs)</span>
<span id="cb60-527"><a href="#cb60-527" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> <span class="dt">Begin</span> label0W (<span class="dt">Choice</span> ws)</span>
<span id="cb60-528"><a href="#cb60-528" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb60-529"><a href="#cb60-529" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb60-530"><a href="#cb60-530" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `u`, `v`, `w`, in reverse:</span></span>
<span id="cb60-531"><a href="#cb60-531" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb60-532"><a href="#cb60-532" aria-hidden="true" tabindex="-1"></a><span class="co">-- u = Begin label0U (Choice us)</span></span>
<span id="cb60-533"><a href="#cb60-533" aria-hidden="true" tabindex="-1"></a><span class="co">-- v = Begin label0V (Choice vs)</span></span>
<span id="cb60-534"><a href="#cb60-534" aria-hidden="true" tabindex="-1"></a><span class="co">-- w = Begin label0W (Choice ws)</span></span>
<span id="cb60-535"><a href="#cb60-535" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> u <span class="op"><*></span> (v <span class="op"><*></span> w)</span></code></pre></div>
<p>The third <code>Applicative</code> law requires that:</p>
<div class="sourceCode" id="cb61"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb61-1"><a href="#cb61-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x <span class="ot">=</span> <span class="fu">pure</span> (f x)</span></code></pre></div>
<p>Proof:</p>
<div class="sourceCode" id="cb62"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb62-1"><a href="#cb62-1" aria-hidden="true" tabindex="-1"></a><span class="fu">pure</span> f <span class="op"><*></span> <span class="fu">pure</span> x</span>
<span id="cb62-2"><a href="#cb62-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb62-3"><a href="#cb62-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`</span></span>
<span id="cb62-4"><a href="#cb62-4" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb62-5"><a href="#cb62-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb62-6"><a href="#cb62-6" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> f empty <span class="op"><*></span> <span class="dt">Begin</span> x empty</span>
<span id="cb62-7"><a href="#cb62-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb62-8"><a href="#cb62-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb62-9"><a href="#cb62-9" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (f x) (<span class="fu">fmap</span> adaptF empty <span class="op"><|></span> <span class="fu">fmap</span> adaptX empty)</span>
<span id="cb62-10"><a href="#cb62-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb62-11"><a href="#cb62-11" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Done</span> resultF) <span class="ot">=</span> <span class="dt">Done</span> resultF</span>
<span id="cb62-12"><a href="#cb62-12" aria-hidden="true" tabindex="-1"></a> adaptF (<span class="dt">Yield</span> labelF restF) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb62-13"><a href="#cb62-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb62-14"><a href="#cb62-14" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> labelF restF <span class="op"><*></span> <span class="dt">Begin</span> x empty</span>
<span id="cb62-15"><a href="#cb62-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb62-16"><a href="#cb62-16" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Done</span> resultX) <span class="ot">=</span> <span class="dt">Done</span> resultX</span>
<span id="cb62-17"><a href="#cb62-17" aria-hidden="true" tabindex="-1"></a> adaptX (<span class="dt">Yield</span> labelX restX) <span class="ot">=</span> <span class="dt">Yield</span> labelFX restFX</span>
<span id="cb62-18"><a href="#cb62-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb62-19"><a href="#cb62-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Begin</span> labelFX restFX <span class="ot">=</span> <span class="dt">Begin</span> f empty <span class="op"><*></span> <span class="dt">Begin</span> labelX restX</span>
<span id="cb62-20"><a href="#cb62-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb62-21"><a href="#cb62-21" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f empty = empty</span></span>
<span id="cb62-22"><a href="#cb62-22" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (f x) (empty <span class="op"><|></span> empty)</span>
<span id="cb62-23"><a href="#cb62-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb62-24"><a href="#cb62-24" aria-hidden="true" tabindex="-1"></a><span class="co">-- empty <|> empty = empty</span></span>
<span id="cb62-25"><a href="#cb62-25" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (f x) empty</span>
<span id="cb62-26"><a href="#cb62-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb62-27"><a href="#cb62-27" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`, in reverse</span></span>
<span id="cb62-28"><a href="#cb62-28" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb62-29"><a href="#cb62-29" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb62-30"><a href="#cb62-30" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (f x)</span></code></pre></div>
<p>The fourth <code>Applicative</code> law requires that:</p>
<div class="sourceCode" id="cb63"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb63-1"><a href="#cb63-1" aria-hidden="true" tabindex="-1"></a>u <span class="op"><*></span> <span class="fu">pure</span> y <span class="ot">=</span> <span class="fu">pure</span> (<span class="op">$</span> y) <span class="op"><*></span> u</span></code></pre></div>
<p>Proof:</p>
<div class="sourceCode" id="cb64"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb64-1"><a href="#cb64-1" aria-hidden="true" tabindex="-1"></a>u <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb64-2"><a href="#cb64-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-3"><a href="#cb64-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- Define:</span></span>
<span id="cb64-4"><a href="#cb64-4" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb64-5"><a href="#cb64-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- u = Begin labelU0 (Choice us)</span></span>
<span id="cb64-6"><a href="#cb64-6" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> labelU0 (<span class="dt">Choice</span> us) <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb64-7"><a href="#cb64-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-8"><a href="#cb64-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`</span></span>
<span id="cb64-9"><a href="#cb64-9" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb64-10"><a href="#cb64-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb64-11"><a href="#cb64-11" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> labelU0 (<span class="dt">Choice</span> us) <span class="op"><*></span> <span class="dt">Begin</span> y empty</span>
<span id="cb64-12"><a href="#cb64-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-13"><a href="#cb64-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`</span></span>
<span id="cb64-14"><a href="#cb64-14" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (labelU0 y) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us <span class="op"><|></span> <span class="fu">fmap</span> adaptY empty)</span>
<span id="cb64-15"><a href="#cb64-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-16"><a href="#cb64-16" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb64-17"><a href="#cb64-17" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUY restUY</span>
<span id="cb64-18"><a href="#cb64-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-19"><a href="#cb64-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> labelUY restUY <span class="ot">=</span> <span class="dt">Begin</span> labelU restU <span class="op"><*></span> <span class="dt">Begin</span> y empty</span>
<span id="cb64-20"><a href="#cb64-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-21"><a href="#cb64-21" aria-hidden="true" tabindex="-1"></a> adaptY (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb64-22"><a href="#cb64-22" aria-hidden="true" tabindex="-1"></a> adaptY (<span class="dt">Yield</span> labelY restY) <span class="ot">=</span> <span class="dt">Yield</span> labelUY restUY</span>
<span id="cb64-23"><a href="#cb64-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-24"><a href="#cb64-24" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> labelUY restUY <span class="ot">=</span> <span class="dt">Begin</span> labelU0 (<span class="dt">Choice</span> us) <span class="op"><*></span> <span class="dt">Begin</span> labelY restY</span>
<span id="cb64-25"><a href="#cb64-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-26"><a href="#cb64-26" aria-hidden="true" tabindex="-1"></a><span class="co">-- fmap f empty = empty</span></span>
<span id="cb64-27"><a href="#cb64-27" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (labelU0 y) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us))</span>
<span id="cb64-28"><a href="#cb64-28" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-29"><a href="#cb64-29" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb64-30"><a href="#cb64-30" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUY restUY</span>
<span id="cb64-31"><a href="#cb64-31" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-32"><a href="#cb64-32" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> labelUY restUY <span class="ot">=</span> <span class="dt">Begin</span> labelU restU <span class="op"><*></span> <span class="dt">Begin</span> y empty</span>
<span id="cb64-33"><a href="#cb64-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-34"><a href="#cb64-34" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`, in reverse</span></span>
<span id="cb64-35"><a href="#cb64-35" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb64-36"><a href="#cb64-36" aria-hidden="true" tabindex="-1"></a><span class="co">-- pure label = Begin label empty</span></span>
<span id="cb64-37"><a href="#cb64-37" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (labelU0 y) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us))</span>
<span id="cb64-38"><a href="#cb64-38" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-39"><a href="#cb64-39" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb64-40"><a href="#cb64-40" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUY restUY</span>
<span id="cb64-41"><a href="#cb64-41" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-42"><a href="#cb64-42" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> labelUY restUY <span class="ot">=</span> <span class="dt">Begin</span> labelU restU <span class="op"><*></span> <span class="fu">pure</span> y</span>
<span id="cb64-43"><a href="#cb64-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-44"><a href="#cb64-44" aria-hidden="true" tabindex="-1"></a><span class="co">-- Induction: u <*> pure y = pure ($ y) <*> u</span></span>
<span id="cb64-45"><a href="#cb64-45" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (labelU0 y) (<span class="dt">Choice</span> (<span class="fu">fmap</span> adaptU us))</span>
<span id="cb64-46"><a href="#cb64-46" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-47"><a href="#cb64-47" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Done</span> result) <span class="ot">=</span> <span class="dt">Done</span> result</span>
<span id="cb64-48"><a href="#cb64-48" aria-hidden="true" tabindex="-1"></a> adaptU (<span class="dt">Yield</span> labelU restU) <span class="ot">=</span> <span class="dt">Yield</span> labelUY restUY</span>
<span id="cb64-49"><a href="#cb64-49" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb64-50"><a href="#cb64-50" aria-hidden="true" tabindex="-1"></a> <span class="dt">Yield</span> labelUY restUY <span class="ot">=</span> <span class="fu">pure</span> (<span class="op">$</span> y) <span class="op"><*></span> <span class="dt">Begin</span> labelU restU</span>
<span id="cb64-51"><a href="#cb64-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-52"><a href="#cb64-52" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `(<*>)`, in reverse</span></span>
<span id="cb64-53"><a href="#cb64-53" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="dt">Begin</span> (<span class="op">$</span> y) empty <span class="op"><*></span> <span class="dt">Begin</span> labelU0 (<span class="dt">Choice</span> us)</span>
<span id="cb64-54"><a href="#cb64-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-55"><a href="#cb64-55" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `pure`, in reverse</span></span>
<span id="cb64-56"><a href="#cb64-56" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (<span class="op">$</span> y) <span class="op"><*></span> <span class="dt">Begin</span> labelU0 (<span class="dt">Choice</span> us)</span>
<span id="cb64-57"><a href="#cb64-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb64-58"><a href="#cb64-58" aria-hidden="true" tabindex="-1"></a><span class="co">-- Definition of `u`, in reverse:</span></span>
<span id="cb64-59"><a href="#cb64-59" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb64-60"><a href="#cb64-60" aria-hidden="true" tabindex="-1"></a><span class="co">-- u = Begin labelU0 (Choice us)</span></span>
<span id="cb64-61"><a href="#cb64-61" aria-hidden="true" tabindex="-1"></a><span class="ot">=</span> <span class="fu">pure</span> (<span class="op">$</span> y) <span class="op"><*></span> u</span></code></pre></div>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-39895878207596587062022-03-12T07:46:00.004-08:002022-03-14T07:23:37.091-07:00The hard part of type-checking Nix<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@GabriellaG439" />
<meta name="twitter:title" content="The hard part of type-checking Nix" />
<meta name="twitter:description" content="An survey on the challenges designing a type system for Nix" />
<title>The hard part of type-checking Nix</title>
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>I’ve been banging my head for a while on the challenge of building a type checker for Nix. The purpose of this post is to summarize my thoughts on this subject so far since they might prove useful to other people grappling with the same problem. In this post I’ll assume familiarity with Nix idioms and the Nix ecosystem.</p>
<h4 id="stack-traces-are-not-enough">Stack traces are not enough</h4>
<p>Nix has one key advantage: purity. This means that you can safely detect errors in code by just running the code (with some caveats I won’t go into) and if anything goes wrong you get a stack trace.</p>
<p>This approach to error detection still has some limitations, which I wrote about in a prior blog post:</p>
<ul>
<li><a href="https://www.haskellforall.com/2021/01/dynamic-type-errors-lack-relevance.html">Dynamic type errors lack relevance</a></li>
</ul>
<p>… but Nix’s stack traces still set the bar for any type-checker, meaning that any proposed type checker needs to produce error messages which are clearer and more informative than the stack traces that Nix currently produces.</p>
<p>“What’s the problem with stack traces?”, you might ask.</p>
<p>The issue is that stack traces do not work well for understanding errors “in the large”. Interpreting a typical stack trace requires a fairly sophisticated mental model of your dependencies, so the more dependencies you have (and the more complicated they are) the more difficulty interpreting the stack trace.</p>
<p>I’ll illustrate this using the following example which is inspired by a real error I ran into at work:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># ./module0.nix</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">{</span> <span class="ex">nixpkgs.overlays</span> = [</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">self:</span> super: {</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="co"># This is a common idiom in Nixpkgs to wrap Haskell packages in</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="co"># `justStaticExecutables` at the top level</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags</span> =</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">self.haskell.lib.justStaticExecutables</span> self.haskellPackages.fast-tags<span class="kw">;</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">)</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> <span class="ex">]</span><span class="kw">;</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<div class="sourceCode" id="cb2"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># ./module1.nix</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="kw">{</span> <span class="ex">nixpkgs.overlays</span> = [</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">self:</span> super: {</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="co"># In a separate overlay, create a variation on the `fast-tags` build with</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> <span class="co"># tests disabled</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">)</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">]</span><span class="kw">;</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<div class="sourceCode" id="cb3"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co"># ./example.nix</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="bu">let</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">nixos</span> = import <span class="op"><</span>nixpkgs/nixos<span class="op">></span> {</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">configuration.imports</span> = [</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">./module1.nix</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">./module0.nix</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">]</span><span class="kw">;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">;</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="er">in</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a> <span class="ex">nixos.pkgs.fast-tags-no-tests</span></span></code></pre></div>
<p>The above <code>./example.nix</code> builds just fine:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix build <span class="at">--file</span> ./example.nix</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> ./result/bin/fast-tags <span class="at">--version</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="ex">fast-tags,</span> version 2.0.1</span></code></pre></div>
<p>… but now suppose that I modify <code>./example.nix</code> to sort the import list:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"># ./example.nix</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="bu">let</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">nixos</span> = import <span class="op"><</span>nixpkgs/nixos<span class="op">></span> {</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">configuration.imports</span> = [</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">./module0.nix</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">./module1.nix</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">]</span><span class="kw">;</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">;</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="er">in</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">nixos.pkgs.fast-tags-no-tests</span></span></code></pre></div>
<p>Now it fails to build, with the following error:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">error:</span> attribute <span class="st">'fast-tags'</span> missing</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /private/tmp/test/module1.nix:5:55:</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">4</span><span class="kw">|</span> <span class="co"># tests disabled</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">5</span><span class="kw">|</span> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">6</span><span class="kw">|</span> <span class="er">})</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="kw">(</span><span class="ex">use</span> <span class="st">'--show-trace'</span> to show detailed location information<span class="kw">)</span></span></code></pre></div>
<p>Okay, so why is the <code>fast-tags</code> attribute missing? Let me add <code>--show-trace</code> like the error message suggests to see what is going wrong:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">error:</span> attribute <span class="st">'fast-tags'</span> missing</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /private/tmp/test/module1.nix:5:55:</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">4</span><span class="kw">|</span> <span class="co"># tests disabled</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">5</span><span class="kw">|</span> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">6</span><span class="kw">|</span> <span class="er">})</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a> <span class="ex">…</span> while evaluating <span class="st">'overrideCabal'</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /nix/store/dchfgixlmxwq0w495w7xc39d65dyqg42-nixpkgs-22.05pre352357.98bb5b77c8c/nixpkgs/pkgs/development/haskell-modules/lib/compose.nix:38:22:</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a> <span class="ex">37</span><span class="kw">|</span> <span class="ex">*/</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a> <span class="ex">38</span><span class="kw">|</span> <span class="ex">overrideCabal</span> = f: drv: <span class="er">(</span><span class="ex">drv.override</span> <span class="er">(</span><span class="ex">args:</span> args // {</span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a> <span class="ex">39</span><span class="kw">|</span> <span class="ex">mkDerivation</span> = drv: <span class="er">(</span><span class="ex">args.mkDerivation</span> drv<span class="kw">)</span><span class="ex">.override</span> f<span class="kw">;</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a> <span class="ex">…</span> from call site</span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /private/tmp/test/module1.nix:5:28:</span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a> <span class="ex">4</span><span class="kw">|</span> <span class="co"># tests disabled</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a> <span class="ex">5</span><span class="kw">|</span> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a> <span class="ex">6</span><span class="kw">|</span> <span class="er">}</span><span class="kw">)</span></span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a> <span class="ex">…</span> while evaluating the attribute <span class="st">'pkgs.fast-tags-no-tests'</span></span>
<span id="cb7-29"><a href="#cb7-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-30"><a href="#cb7-30" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /private/tmp/test/module1.nix:5:7:</span>
<span id="cb7-31"><a href="#cb7-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-32"><a href="#cb7-32" aria-hidden="true" tabindex="-1"></a> <span class="ex">4</span><span class="kw">|</span> <span class="co"># tests disabled</span></span>
<span id="cb7-33"><a href="#cb7-33" aria-hidden="true" tabindex="-1"></a> <span class="ex">5</span><span class="kw">|</span> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb7-34"><a href="#cb7-34" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb7-35"><a href="#cb7-35" aria-hidden="true" tabindex="-1"></a> <span class="ex">6</span><span class="kw">|</span> <span class="er">}</span><span class="kw">)</span></span>
<span id="cb7-36"><a href="#cb7-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-37"><a href="#cb7-37" aria-hidden="true" tabindex="-1"></a> <span class="ex">…</span> while evaluating the file <span class="st">'./example.nix'</span>:</span></code></pre></div>
<p>Hmmm 🤔. That still does really not explain what went wrong.</p>
<p>The <em>actual</em> reason for the type error is that the order of overlays matters, but there’s no way that you would know that from looking at the stack trace. You would have to first understand how NixOS modules work and how the Nixpkgs overlay system works in order to correctly pinpoint the problem and even then it would still be tricky. I know because I am an experienced Nix user myself yet I still stumbled on an error message like this, albeit in the context of a larger codebase.</p>
<p>Moreover, the above example is not a contrived example that I hand-picked to make Nix look bad. This is idiomatic Nix code that uses modern conventions from Nixpkgs and NixOS exactly as the maintainers intended them to be used. The only thing that’s not idiomatic is using the NixOS module system to build just one package instead of building an entire NixOS system, but that’s only because I minimized this example from real code that built a complete NixOS system.</p>
<h4 id="types-to-the-rescue">Types to the rescue?</h4>
<p>There’s a deeper issue, though, which is that even if Nix had a type system the equivalent type error would have been almost as uninformative!</p>
<p>To see why, let’s imagine what a hypothetical informative type error might have looked like.</p>
<p>A great type error (for a command-line type checker) would have been something like this:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">The</span> fast-tags attribute defined within this overlay:</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">./module0.nix:7:6:</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">↓</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags</span> =</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">self.haskell.lib.justStaticExecutables</span> self.haskellPackages.fast-tags<span class="kw">;</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span> is not in scope within this other overlay:</span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">./module1.nix:7:6:</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">↓</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="ex">…</span> because the latter overlay is ordered before the former overlay.</span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="ex">Suggestion:</span> Perhaps wrap the latter overlay in <span class="kw">`</span><span class="ex">lib.mkAfter</span><span class="kw">`</span>, like this:</span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a><span class="kw">{</span> <span class="ex">lib,</span> ... }:</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a><span class="kw">{</span> <span class="ex">nixpkgs.overlays</span> = lib.mkAfter [</span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">self:</span> super: {</span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a> <span class="co"># In a separate overlay, create a variation on the `fast-tags` build with</span></span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a> <span class="co"># tests disabled</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span> = self.haskell.lib.dontCheck super.fast-tags<span class="kw">;</span></span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">)</span></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a> <span class="ex">]</span><span class="kw">;</span></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>Nobody expects a type-checker for Nix to be <em>that</em> smart, but it’s instructive to consider why not.</p>
<p>Obviously, there’s no way for the type-checker to know that the user’s intent was to refer to the <code>fast-tags</code> attribute in some other overlay defined within some unrelated file. Or is there?</p>
<p>Well, what if we had written the following simpler example that doesn’t use overlays and instead uses ordinary <code>let</code> definitions to build up the final desired package:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="bu">let</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs</span> = import <span class="op"><</span>nixpkgs<span class="op">></span> { }<span class="kw">;</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags</span> =</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs.haskell.lib.justStaticExecutables</span> pkgs.haskellPackages.fast-tags<span class="kw">;</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span> =</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs.haskell.lib.dontCheck</span> fast-tags<span class="kw">;</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="er">in</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span></span></code></pre></div>
<p>As an aside, let’s admire how much more clear that example is. However, there are good reasons why Nixpkgs discourages this approach in the large which I won’t get into.</p>
<p>Now let’s take that example and reorder the two <code>let</code> bindings:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="bu">let</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs</span> = import <span class="op"><</span>nixpkgs<span class="op">></span> { }<span class="kw">;</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span> =</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs.haskell.lib.dontCheck</span> fast-tags<span class="kw">;</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags</span> =</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs.haskell.lib.justStaticExecutables</span> pkgs.haskellPackages.fast-tags<span class="kw">;</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a><span class="er">in</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span></span></code></pre></div>
<p>Well, actually that still works 😅 because Nix permits out-of-order <code>let</code> bindings so long as they are in the same <code>let</code> block, but we can force the ordering by nesting the latter <code>let</code> binding:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="bu">let</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs</span> = import <span class="op"><</span>nixpkgs<span class="op">></span> { }<span class="kw">;</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span> =</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs.haskell.lib.dontCheck</span> fast-tags<span class="kw">;</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="er">in</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a> <span class="bu">let</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags</span> =</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs.haskell.lib.justStaticExecutables</span> pkgs.haskellPackages.fast-tags<span class="kw">;</span></span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a> <span class="er">in</span></span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">fast-tags-no-tests</span></span></code></pre></div>
<p>… which gives the following error we expected:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">error:</span> undefined variable <span class="st">'fast-tags'</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /private/tmp/test/example.nix:5:32:</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">4</span><span class="kw">|</span> <span class="ex">fast-tags-no-tests</span> =</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">5</span><span class="kw">|</span> <span class="ex">pkgs.haskell.lib.dontCheck</span> fast-tags<span class="kw">;</span></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">6</span><span class="kw">|</span></span></code></pre></div>
<p>This type error is already easier for the user to interpret, not because of any type system, but rather because using ordinary <code>let</code> bindings forced us to structure our code so that a human can more easily discern what’s wrong. Now the <code>let</code> bindings are plainly out of order, lexically.</p>
<p>Moreover, now a type-checker can easily look ahead and detect that there was a <code>fast-tags</code> identifier defined shortly downstream that the user might have intended to use, so it’s completely realistic to expect an error message like this:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">error:</span> undefined variable <span class="st">'fast-tags'</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">at</span> /private/tmp/test/example.nix:5:32:</span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">4</span><span class="kw">|</span> <span class="ex">fast-tags-no-tests</span> =</span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">5</span><span class="kw">|</span> <span class="ex">pkgs.haskell.lib.dontCheck</span> fast-tags<span class="kw">;</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">6</span><span class="kw">|</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a><span class="ex">Hint:</span> Did you mean to refer to this <span class="kw">`</span><span class="ex">fast-tags</span><span class="kw">`</span> identifier defined afterwards</span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">within</span> the same file<span class="pp">?</span></span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">9</span><span class="kw">|</span> <span class="ex">fast-tags</span> =</span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">|</span> <span class="ex">^</span></span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true" tabindex="-1"></a> <span class="ex">10</span><span class="kw">|</span> <span class="ex">pkgs.haskell.lib.justStaticExecutables</span> pkgs.haskellPackages.fast-tags<span class="kw">;</span></span></code></pre></div>
<p>By restructuring definitions as ordinary <code>let</code> bindings we’ve greatly reduced the difficulty of producing a great error message. No longer does the type-checker require a deep understanding of Nixpkgs overlays or NixOS modules; the type-checker only needs to understand how Nix the language works (specifically <code>let</code> bindings).</p>
<p>Equally important, <strong>the end user</strong> also no longer requires an understanding of Nixpkgs overlays or NixOS modules to understand the type error. Even our hypothetical “great” error message still required our user to have some literacy with Nixpkgs and NixOS in order to interpret the error (albeit, not as much as the original stack trace).</p>
<h4 id="the-actual-problem">The actual problem</h4>
<p>The real issue with Nix isn’t the lack of a type checker. The absence of a type-checker is problematic, but in my view this is a symptom of a larger issue.</p>
<p>The fundamental problem that plagues all type-checking attempts for Nix is that nobody actually uses <strong>Nix the language</strong> at any significant scale. Instead, the community has adopted two sub-languages embedded within Nix for programming “in the large”:</p>
<ul>
<li><p>Nixpkgs overlays</p>
<p>This is an embedded language that simulates object-oriented programming with inheritance / late binding / dynamic scope (depending on how you think about it)</p></li>
<li><p>NixOS modules</p>
<p>This is an embedded language that roughly emulates <a href="https://github.com/hashicorp/terraform">Terraform</a></p></li>
</ul>
<p>Carefully note that these are not language features built into Nix; rather they are embedded domain-specific languages implemented within Nix. Consequently, a type-checker for “Nix the language” is not necessarily equipped to type-check these two sub-languages.</p>
<h4 id="what-about-row-polymorphism">What about row polymorphism?</h4>
<p>Let’s focus on one of those two sub-languages, the Nixpkgs overlay system, to see if we can still salvage things somewhat. The reason I suggest this is because the Nixpkgs overlay system is the far simpler sub-language of the two and the implementation of overlays is simple and tiny.</p>
<p>Technically, all you need to type-check the Nixpkgs overlays system is type system support for anonymous records and Nix’s <code>//</code> operator. Modern type systems can support both of those features through the use of <a href="https://en.wikipedia.org/wiki/Row_polymorphism">row polymorphism</a>. This section assumes familiarity with row polymorphism, and you can skip this section if you aren’t familiar (it’s not essential), but if you would like to learn more, you can read the following resources:</p>
<ul>
<li><a href="https://hgiasac.github.io/posts/2018-11-18-Record-Row-Type-and-Row-Polymorphism.html">Record, Row Types, and Row Polymorphism</a></li>
<li><a href="https://ckoster22.medium.com/advanced-types-in-elm-extensible-records-67e9d804030d">Advanced Types in Elm: Extensible Records</a></li>
</ul>
<p>Most people who are familiar with row polymorphism will wonder: can we use a type system with support for row polymorphism to type-check Nixpkgs overlays?</p>
<p>The answer is: sort of. We run into the same problems as stack traces: we catch errors, but the error messages and inferred types are less informative than we would hope.</p>
<p>To illustrate this, consider the default entrypoint to Nixpkgs, which is a function that takes an <code>overlays</code> argument containing a list of overlays, like this:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a> overlay0 <span class="ot">=</span> self<span class="op">:</span> super<span class="op">:</span> {</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a> cowsay <span class="ot">=</span> super<span class="op">.</span>cowsay<span class="op">.</span>overrideAttrs (old<span class="op">:</span> {</span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a> doCheck <span class="ot">=</span> false;</span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> });</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a> };</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a> overlay1 <span class="ot">=</span> self<span class="op">:</span> super<span class="op">:</span> {</span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a> hello <span class="ot">=</span> super<span class="op">.</span>hello<span class="op">.</span>overrideAttrs (old<span class="op">:</span> {</span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a> postInstall <span class="ot">=</span> <span class="st">"${self.cowsay}/bin/cowsay 'Installation complete'"</span>;</span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a> });</span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a> };</span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-14"><a href="#cb14-14" aria-hidden="true" tabindex="-1"></a> pkgs <span class="ot">=</span> <span class="kw">import</span> <nixpkgs> { overlays = [ overlay0 overlay1 ]; };</span>
<span id="cb14-15"><a href="#cb14-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-16"><a href="#cb14-16" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb14-17"><a href="#cb14-17" aria-hidden="true" tabindex="-1"></a> pkgs<span class="op">.</span>hello</span></code></pre></div>
<p>Now, ask yourself the following questions:</p>
<ul>
<li><p>What would be the type of an overlay?</p></li>
<li><p>What would be the type of a list of overlays?</p>
<p>… such as <code>[ overlay0 overlay1 ]</code> in the above example</p></li>
<li><p>What type should the <code><nixpkgs></code> function expect for its <code>overlays</code> argument?</p></li>
<li><p>How do the answers to the above questions affect inferred types and error messages?</p></li>
</ul>
<p>I found these questions surprisingly difficult to answer! Again, the above example is not a contrived example I chose as a type-checking puzzle. This is the recommended way to use Nixpkgs.</p>
<p>I will go ahead and share what I think are the “right” answers that will get us closest to a type-checker for Nixpkgs with the fewest intrusive changes:</p>
<ul>
<li><p>What would be the type of an overlay?</p>
<p>There would be no such thing as an “overlay type”. Each overlay would have a distinct type, but in every case an overlay would be a function from strongly-typed and row-polymorphic record inputs to a strongly-typed record output.</p>
<p>For example, the types of the above <code>overlay0</code> and <code>overlay1</code> functions would be something like this (using a pseudo-notation I made up inspired by <a href="https://github.com/Gabriel439/grace">Fall-from-Grace</a> because no existing language has great syntax for this):</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="ex">overlay0</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">:</span> forall <span class="er">(</span><span class="ex">a</span> b c : Fields<span class="kw">)</span> <span class="kw">(</span><span class="ex">o</span> : Type<span class="kw">)</span></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a> <span class="bu">.</span> { a }</span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay :</span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">overrideAttrs</span> :</span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">o</span> <span class="at">-</span><span class="op">></span> { doCheck : Bool }<span class="kw">)</span> <span class="ex">-</span><span class="op">></span> { c, doCheck : Bool }</span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> c</span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">}</span></span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> b</span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span></span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay : { c, doCheck : Bool } }</span>
<span id="cb15-12"><a href="#cb15-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-13"><a href="#cb15-13" aria-hidden="true" tabindex="-1"></a><span class="ex">overlay1</span></span>
<span id="cb15-14"><a href="#cb15-14" aria-hidden="true" tabindex="-1"></a> <span class="bu">:</span> forall <span class="er">(</span><span class="ex">a</span> b c : Fields<span class="kw">)</span> <span class="kw">(</span><span class="ex">o</span> : Type<span class="kw">)</span></span>
<span id="cb15-15"><a href="#cb15-15" aria-hidden="true" tabindex="-1"></a> <span class="bu">.</span> { a }</span>
<span id="cb15-16"><a href="#cb15-16" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { hello :</span>
<span id="cb15-17"><a href="#cb15-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">overrideAttrs</span> :</span>
<span id="cb15-18"><a href="#cb15-18" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">o</span> <span class="at">-</span><span class="op">></span> { postInstall : Text }<span class="kw">)</span> <span class="ex">-</span><span class="op">></span> { c, postInstall : Text }</span>
<span id="cb15-19"><a href="#cb15-19" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> c</span>
<span id="cb15-20"><a href="#cb15-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">}</span></span>
<span id="cb15-21"><a href="#cb15-21" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> b</span>
<span id="cb15-22"><a href="#cb15-22" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span></span>
<span id="cb15-23"><a href="#cb15-23" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { hello : { c, postInstall : Text } }</span></code></pre></div>
<p>Don’t sweat it if you don’t follow that notation I just made up. The purpose is just to informally illustrate that we can assign row-polymorphic types to <code>overlay0</code> and <code>overlay1</code>.</p></li>
<li><p>What would be the type of a list of overlays?</p>
<p>There would be no such thing as a list of overlays, for the same reason that there would be no “overlay type”.</p>
<p>To be totally pedantic, you could in theory model a list of heterogeneously-typed overlays using a type-indexed list, but I don’t believe that’s the right path forward for Nixpkgs.</p></li>
<li><p>What type should the <code><nixpkgs></code> function expect for its <code>overlays</code> argument?</p>
<p>The <code><nixpkgs></code> entrypoint function would no longer take a list of overlays (because there would be no such thing as a list of overlays). It would take a single overlay as input and you would have to precompose overlays using <code>lib.composeExtensions</code> if you wanted to supply more than one overlay as input to <code><nixpkgs></code>.</p>
<p>For example, if you were to compose <code>overlay0</code> and <code>overlay1</code>, you would end up with a new composite overlay of the following type:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="ex">lib.composeExtensions</span> overlay0 overlay1</span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">:</span> forall <span class="er">(</span><span class="ex">a</span> b c d : Fields<span class="kw">)</span> <span class="kw">(</span><span class="ex">o</span> : Type<span class="kw">)</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a> <span class="bu">.</span> { a }</span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay :</span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">overrideAttrs</span> :</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">o</span> <span class="at">-</span><span class="op">></span> { doCheck : Bool }<span class="kw">)</span> <span class="ex">-</span><span class="op">></span> { c, doCheck : Bool }</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> c</span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">}</span></span>
<span id="cb16-9"><a href="#cb16-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">hello</span> : </span>
<span id="cb16-10"><a href="#cb16-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">overrideAttrs</span> :</span>
<span id="cb16-11"><a href="#cb16-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">(</span><span class="ex">o</span> <span class="at">-</span><span class="op">></span> { postInstall : Text }<span class="kw">)</span> <span class="ex">-</span><span class="op">></span> { d, postInstall : Text }</span>
<span id="cb16-12"><a href="#cb16-12" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> d</span>
<span id="cb16-13"><a href="#cb16-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">}</span></span>
<span id="cb16-14"><a href="#cb16-14" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> b</span>
<span id="cb16-15"><a href="#cb16-15" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span></span>
<span id="cb16-16"><a href="#cb16-16" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay : { c, doCheck : Bool }, hello : { d, postInstall : Text } }</span></code></pre></div>
<p>And the <code><nixpkgs></code> entrypoint function type would be something like:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="ex">import</span> <span class="op"><</span>nixpkgs<span class="op">></span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">:</span> forall <span class="er">(</span><span class="ex">a</span> : Fields<span class="kw">)</span></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">overlay</span> : </span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">cowsay</span> : …, hello : …, …, a } <span class="co"># Enormous type goes here</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay : …, hello : …, … } <span class="co"># Similarly enormous type</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { a }</span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> … <span class="co"># Other <nixpkgs> arguments we'll ignore for now</span></span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">}</span></span>
<span id="cb17-9"><a href="#cb17-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay : …, hello : …, …, a }</span></code></pre></div></li>
<li><p>How do the answers to the above questions affect inferred types and error messages?</p>
<p>The inferred types would be enormous (especially for the <code><nixpkgs></code> entrypoint function). They might even be infinitely large without some form of information hiding, because these derivations are infinitely deep records that refer to themselves:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix repl</span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="ex">nix-repl</span><span class="op">></span> pkgs = import <span class="op"><</span>nixpkgs<span class="op">></span> { }</span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a><span class="ex">nix-repl</span><span class="op">></span> pkgs.hello.out.out.out.out.out.… <span class="co"># … ad infinitum</span></span></code></pre></div>
<p>The error messages would be <em>slightly</em> better than current stack traces but would still have great difficulty explaining to the user what actually went wrong because the type-checker is still not operating on the same abstraction level as the overlay system. The main improvement over stack traces is that the user would now be able to annotate expressions with types to help narrow down the cause of type errors.</p></li>
</ul>
<p>I would like to dwell a little bit on the incredibly large inferred type for the <code><nixpkgs></code> entrypoint, because this is essentially the same problem as the one highlighted in the previous section.</p>
<p>Implementing the Nixpkgs overlay system as an embedded language within Nix means that implementation details (like scope) are exposed to the user in a way that they wouldn’t have been exposed to if they were built-in language features.</p>
<p>For example, this type:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a> <span class="ex">import</span> <span class="op"><</span>nixpkgs<span class="op">></span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">:</span> forall <span class="er">(</span><span class="ex">a</span> : Fields<span class="kw">)</span></span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">overlay</span> : </span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">{</span> <span class="ex">cowsay</span> : …, hello : …, …, a } <span class="co"># Enormous type goes here</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay : …, hello : …, … } <span class="co"># Similarly enormous type</span></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { a }</span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">,</span> … <span class="co"># Other <nixpkgs> arguments we'll ignore for now</span></span>
<span id="cb19-8"><a href="#cb19-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">}</span></span>
<span id="cb19-9"><a href="#cb19-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">-</span><span class="op">></span> { cowsay : …, hello : …, …, a }</span></code></pre></div>
<p>… is essentially saying “our input overlay is a value that has all of these packages in scope (such as <code>cowsay</code>, <code>hello</code>, and every other package from Nixpkgs), and then adds some new packages to the scope.</p>
<p>That’s cool and all that we can reify this information in the language and the type, but I want to point out how awkward this is compared to good old-fashioned lexical scope natively supported by the Nix language. Let’s revisit the same example using plain-old <code>let</code> bindings:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="bu">let</span></span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">pkgs</span> = import <span class="op"><</span>nixpkgs<span class="op">></span> { }<span class="kw">;</span></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">cowsay</span> = pkgs.cowsay.overrideAttrs <span class="er">(</span><span class="ex">old:</span> {</span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">doCheck</span> = false<span class="kw">;</span></span>
<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">);</span></span>
<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">hello</span> = pkgs.hello.overrideAttrs <span class="er">(</span><span class="ex">old:</span> {</span>
<span id="cb20-9"><a href="#cb20-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">postInstall</span> = <span class="st">"</span><span class="va">${cowsay}</span><span class="st">/bin/cowsay 'Installation complete'"</span><span class="kw">;</span></span>
<span id="cb20-10"><a href="#cb20-10" aria-hidden="true" tabindex="-1"></a> <span class="er">}</span><span class="kw">);</span></span>
<span id="cb20-11"><a href="#cb20-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-12"><a href="#cb20-12" aria-hidden="true" tabindex="-1"></a><span class="er">in</span></span>
<span id="cb20-13"><a href="#cb20-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">hello</span></span></code></pre></div>
<p>If I were to ask the type-checker what the type of <code>cowsay</code> is, I would hope that the type-checker would respond with something like:</p>
<blockquote>
<p><code>cowsay</code> is a <code>Derivation</code></p>
</blockquote>
<p>… and not:</p>
<blockquote>
<p><code>cowsay</code> is an expression that begins from a context containing a value named <code>pkgs</code> of this enormous type and then ends with a new context that is also enormous, but now extended with an additional value named <code>cowsay</code> of type <code>Derivation</code>.</p>
</blockquote>
<p>You see the problem here? By implementing this overlays sub-language within Nix we’re needlessly polluting the user experience with internal implementation details of that sub-language (such as scope and context). This is why a type-checker for Nix is not adequately equipped to explain to the user what is happening within the Nixpkgs overlay system because it’s type-checking things at the wrong abstraction level.</p>
<h4 id="conclusion">Conclusion</h4>
<p>So what’s the path forward for Nix? I’m not entirely sure, but here is what I think are the possible outcomes, in roughly ascending order of difficulty:</p>
<ul>
<li><p><strong>Solution A:</strong> Don’t implement a type system for Nix</p>
<p>Instead, try to improve the quality of stack traces and dynamic type errors.</p>
<p>For example, this is essentially the approach taken by the recent work to improve the user experience for stack traces:</p>
<ul>
<li><a href="https://github.com/bburdette/nix-error-proposal/blob/master/proposal.md">Nix Error Enhancement</a></li>
</ul></li>
<li><p><strong>Solution B:</strong> Only type-check Nix “in the small”</p>
<p>This seems to be the approach that <a href="https://github.com/tweag/nickel">Nickel</a> is taking and this also seems to be the same way that people are using <a href="https://www.haskellforall.com/2017/01/typed-nix-programming-using-dhall.html">dhall-to-nix</a>. Users settle for type-checking smaller fragments of Nix (like individual packages or NixOS modules), but any issues that arise across package/module boundaries are still not effectively addressed.</p></li>
<li><p><strong>Solution C:</strong> Type-check Nixpkgs overlays using a type system supporting row polymorphism</p>
<p>Some users might be willing to settle for really complicated row-polymorphic types as long as they can still statically detect packaging issues in the large.</p>
<p>I haven’t yet thought through how to type-check the NixOS module system as an embedded language, so I’m leaving that out for now, but it might be possible to also type-check that using row polymorphism 🤷🏻♀️.</p></li>
<li><p><strong>Solution D:</strong> Implement the two sub-languages in an external language</p>
<p>In other words, implement the Nixpkgs overlay system and NixOS module system in a separate language that is not Nix so that overlays and modules are supported by the language along with a type system that natively understands these features. Then you could compile this external language to ordinary Nix code that is compatible with the existing Nixpkgs overlay system or NixOS module system.</p></li>
<li><p><strong>Solution E:</strong> Like Solution D, but upstream these features into the Nix language</p>
<p>You could add Nix language support for overlays/modules, instead of implementing them as embedded domain-specific languages.</p></li>
<li><p><strong>Solution F:</strong> Like Solution D, but without Nix as an intermediate language</p>
<p>Basically, design a new front-end programming language for the Nix store that isn’t Nix. This language would natively understand overlays and NixOS modules and would generate derivations directly without going through Nix as an intermediate language.</p></li>
<li><p><strong>Solution G:</strong> Don’t use overlays or NixOS modules at all</p>
<p>In other words, rethink the ecosystem from the ground up to use plain old Nix the language. Obviously, this is a massive amount of work to reinvent all of Nixpkgs and NixOS and it’s not clear that people would even want this.</p></li>
</ul>
<p>My best guess is that “Solution C” or “Solution D” are the two most promising approaches that strike the right balance between how difficult they are to implement and actually addressing what users want in a type-checker.
</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com0tag:blogger.com,1999:blog-1777990983847811806.post-19027346942505443062022-03-02T07:16:00.009-08:002023-09-03T08:56:16.907-07:00Applicatives should usually implement Semigroup and Monoid<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>lift-monoid</title>
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@flickr" />
<meta name="twitter:title" content="Applicatives should usually implement Semigroup and Monoid" />
<meta name="twitter:description" content="A post that explains why and when Applicative type constructors should provide lifted Semigroup and Monoid instances" />
<style>
html {
line-height: 1.5;
font-family: Georgia, serif;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<p>The gist of this post is that any type constructor <code>F</code> that implements <code>Applicative</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> <span class="dt">F</span> <span class="kw">where</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> …</span></code></pre></div>
<p>… should <em>usually</em> also implement the following <code>Semigroup</code> and <code>Monoid</code> instances:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Semigroup</span> a <span class="ot">=></span> <span class="dt">Semigroup</span> (<span class="dt">F</span> a) <span class="kw">where</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> (<span class="op"><></span>) <span class="ot">=</span> liftA2 (<span class="op"><></span>)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> a <span class="ot">=></span> <span class="dt">Monoid</span> (<span class="dt">F</span> a) <span class="kw">where</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">mempty</span> <span class="ot">=</span> <span class="fu">pure</span> <span class="fu">mempty</span></span></code></pre></div>
<p>… which one can also derive using <a href="https://hackage.haskell.org/package/base/docs/Data-Monoid.html#t:Ap">the <code>Data.Monoid.Ap</code> type</a>, which was created for this purpose:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> (<span class="dt">Semigroup</span>, <span class="dt">Monoid</span>) via (<span class="dt">Ap</span> <span class="dt">F</span> a)</span></code></pre></div>
<p>Since each type constructor that implements <code>Monad</code> also implements <code>Applicative</code>, this recommendation also applies for all <code>Monad</code>s, too.</p>
<h4 id="why-are-these-instances-useful">Why are these instances useful?</h4>
<p>The above instances come in handy in conjunction with utilities from Haskell’s standard library that work with <code>Monoid</code>s.</p>
<p>For example, a common idiom I see when doing code review is something like this:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monad</span> <span class="dt">M</span> <span class="kw">where</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> …</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="ot">example ::</span> <span class="dt">M</span> [<span class="dt">B</span>]</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>example <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span><span class="ot"> process ::</span> <span class="dt">A</span> <span class="ot">-></span> <span class="dt">M</span> [<span class="dt">B</span>]</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a> process a <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a> …</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> bs</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span><span class="ot"> inputs ::</span> [<span class="dt">A</span>]</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a> inputs <span class="ot">=</span> …</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a> bss <span class="ot"><-</span> <span class="fu">mapM</span> process inputs</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (<span class="fu">concat</span> bss)</span></code></pre></div>
<p>… but if you implemented the suggested <code>Semigroup</code> and <code>Monoid</code> instances then you could replace this:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a> bss <span class="ot"><-</span> <span class="fu">mapM</span> process inputs</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (<span class="fu">concat</span> bss)</span></code></pre></div>
<p>… with this:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a> <span class="fu">foldMap</span> process inputs</span></code></pre></div>
<p>These instances also come in handy when you need to supply an empty action or empty handler for some callback.</p>
<p>For example, the <code>lsp</code> package provides a <code>sendRequest</code> utility which has the following type:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>sendRequest</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ot"> ::</span> <span class="dt">MonadLsp</span> config f</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> <span class="ot">=></span> <span class="dt">SServerMethod</span> m</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">MessageParams</span> m</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> (<span class="dt">Either</span> <span class="dt">ResponseError</span> (<span class="dt">ResponseResult</span> m) <span class="ot">-></span> f ())</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ^ This is the callback function</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> f (<span class="dt">LspId</span> m)</span></code></pre></div>
<p>I won’t go into too much detail about what the type means other than to point out that this function lets a language server send a request to the client and then execute a callback function when the client responds. The callback function you provide has type:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="dt">Either</span> <span class="dt">ResponseError</span> (<span class="dt">ResponseResult</span> m) <span class="ot">-></span> f ()</span></code></pre></div>
<p>Sometimes you’re not interested in the client’s response, meaning that you want to supply an empty callback that does nothing. Well, if the type constructor <code>f</code> implements the suggested <code>Monoid</code> instance then the empty callback is: <code>mempty</code>.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="fu">mempty</span><span class="ot"> ::</span> <span class="dt">Either</span> <span class="dt">ResponseError</span> (<span class="dt">ResponseResult</span> m) <span class="ot">-></span> f ()</span></code></pre></div>
<p>… and this works because of the following three <code>Monoid</code> instances that are automatically chained together by the compiler:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> ()</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- The suggested Monoid instance that `f` would ideally provide</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> a <span class="ot">=></span> <span class="dt">Monoid</span> (f a)</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Monoid</span> b <span class="ot">=></span> <span class="dt">Monoid</span> (a <span class="ot">-></span> b)</span></code></pre></div>
<p>In fact, certain <code>Applicative</code>/<code>Monad</code>-related utilites become special cases of simpler <code>Monoid</code>-related utilities once you have this instance. For example:</p>
<ul>
<li><p>You can sometimes replace <a href="https://hackage.haskell.org/package/base/docs/Data-Foldable.html#v:traverse_"><code>traverse_</code></a> / <a href="https://hackage.haskell.org/package/base/docs/Control-Monad.html#v:mapM_"><code>mapM_</code></a> with the simpler <a href="https://hackage.haskell.org/package/base/docs/Data-Foldable.html#v:foldMap"><code>foldMap</code></a> utility</p>
<p>Specifically, if you specialize the type of <code>traverse_</code> / <code>mapM_</code> to:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="fu">traverse_</span><span class="ot"> ::</span> (<span class="dt">Foldable</span> t, <span class="dt">Applicative</span> f) <span class="ot">=></span> (a <span class="ot">-></span> f ()) <span class="ot">-></span> t a <span class="ot">-></span> f ()</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="fu">mapM_</span><span class="ot"> ::</span> (<span class="dt">Foldable</span> t, <span class="dt">Monad</span> f) <span class="ot">=></span> (a <span class="ot">-></span> f ()) <span class="ot">-></span> t a <span class="ot">-></span> f ()</span></code></pre></div>
<p>… then <code>foldMap</code> behaves the same way when the <code>Applicative</code> <code>f</code> implements the suggested instances:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="fu">foldMap</span><span class="ot"> ::</span> (<span class="dt">Foldable</span> t, <span class="dt">Monoid</span> m) <span class="ot">=></span> (a <span class="ot">-></span> m) <span class="ot">-></span> t a <span class="ot">-></span> m</span></code></pre></div></li>
<li><p>You can sometimes replace <a href="https://hackage.haskell.org/package/base/docs/Data-Foldable.html#v:sequenceA_">sequenceA_</a> / <a href="https://hackage.haskell.org/package/base/docs/Control-Monad.html#v:sequence_"><code>sequence_</code></a> with the simpler <a href="https://hackage.haskell.org/package/base/docs/Data-Foldable.html#v:fold"><code>fold</code></a> utility</p>
<p>Specifically, if you specialize the type of <code>sequenceA_</code> / <code>sequence_</code> to:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ot">sequenceA_ ::</span> (<span class="dt">Foldable</span> t, <span class="dt">Applicative</span> f) <span class="ot">-></span> t (f ()) <span class="ot">-></span> f ()</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="fu">sequence_</span><span class="ot"> ::</span> (<span class="dt">Foldable</span> t, <span class="dt">Monad</span> f) <span class="ot">-></span> t (f ()) <span class="ot">-></span> f ()</span></code></pre></div>
<p>… then <code>fold</code> behaves the same way when the <code>Applicative</code> <code>f</code> implements the’ suggested instances:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fold ::</span> (<span class="dt">Foldable</span> t, <span class="dt">Monoid</span> m) <span class="ot">=></span> t m <span class="ot">-></span> m</span></code></pre></div></li>
<li><p>You can sometimes replace <a href="https://hackage.haskell.org/package/base/docs/Control-Monad.html#v:replicateM_"><code>replicateM_</code></a> with <a href="https://hackage.haskell.org/package/base/docs/Data-Semigroup.html#v:mtimesDefault"><code>mtimesDefault</code></a></p>
<p>Specifically, if you specialize the type of <code>replicateM_</code> to:</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="ot">replicateM_ ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> <span class="dt">Int</span> <span class="ot">-></span> f () <span class="ot">-></span> f ()</span></code></pre></div>
<p>… then <code>mtimesDefault</code> behaves the same way when the <code>Applicative</code> <code>f</code> implements the suggested instances:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="ot">mtimesDefault ::</span> <span class="dt">Monoid</span> m <span class="ot">=></span> <span class="dt">Int</span> <span class="ot">-></span> m <span class="ot">-></span> m</span></code></pre></div></li>
</ul>
<p>And you also gain access to new functionality which doesn’t currently exist in <code>Control.Monad</code>. For example, the following specializations hold when <code>f</code> implements the suggested instances:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- This specialization is similar to the original `foldMap` example</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="ot">fold ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> [f [b]] <span class="ot">-></span> f [b]</span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- You can combine two handlers into a single handler</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a><span class="ot">(<>) ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> (a <span class="ot">-></span> f ()) <span class="ot">-></span> (a <span class="ot">-></span> f ()) <span class="ot">-></span> (a <span class="ot">-></span> f ())</span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- a.k.a. `pass` in the `relude` package</span></span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a><span class="fu">mempty</span><span class="ot"> ::</span> <span class="dt">Applicative</span> f <span class="ot">=></span> f ()</span></code></pre></div>
<h4 id="when-should-one-not-do-this">When should one not do this?</h4>
<p>You sometimes don’t want to implement the suggested <code>Semigroup</code> and <code>Monoid</code> instances when other law-abiding instances are possible. For example, sometimes the <code>Applicative</code> type constructor permits a different <code>Semigroup</code> and <code>Monoid</code> instance.</p>
<p>The classic example is lists, where the <code>Semigroup</code> / <code>Monoid</code> instances behave like list concatenation. Also, most of the exceptions that fall in this category are list-like, in the sense that they use the <code>Semigroup</code> / <code>Monoid</code> instances to model some sort of element-agnostic concatenation.</p>
<p>I view these “non-lifted” <code>Monoid</code> instances as a missed opportunity, because these same type constructors will typically also implement the exact same behavior for their <code>Alternative</code> instance, too, like this:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Alternative</span> <span class="dt">SomeListLikeType</span> <span class="kw">where</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a> empty <span class="ot">=</span> <span class="fu">mempty</span></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a> (<span class="op"><|></span>) <span class="ot">=</span> (<span class="op"><></span>)</span></code></pre></div>
<p>… which means that you have two instances doing the exact same thing, when one of those instances could have potentially have been used to support different functionality. I view the <code>Alternative</code> instance as the more natural instance for element-agnostic concatenation since that is the only behavior the <code>Alternative</code> class signature permits. By process of elimination, the <code>Monoid</code> and <code>Semigroup</code> instances should in principle be reserved for the “lifted” implementation suggested by this post.</p>
<p>However, I also understand it would be far too disruptive at this point to change these list-like <code>Semigroup</code> and <code>Monoid</code> instances and expectations around them, so I think the pragmatic approach is to preserve the current Haskell ecosystem conventions, even if they strike me as less elegant.</p>
<h4 id="why-not-use-ap-exclusively">Why not use <code>Ap</code> exclusively?</h4>
<p>The most commonly cited objection to these instances is that you technically don’t need to add these lifted <code>Semigroup</code> and <code>Monoid</code> instances because you can access them “on the fly” by wrapping expressions in the <code>Ap</code> newtype before combining them.</p>
<p>For example, even if we didn’t have a <code>Semigroup</code> and <code>Monoid</code> instance, we could still write our original example using <code>foldMap</code>, albeit with more newtype-coercion boilerplate:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a> <span class="fu">fmap</span> getAp (<span class="fu">foldMap</span> process (<span class="fu">map</span> <span class="dt">Ap</span> inputs))</span></code></pre></div>
<p>… or perhaps using the <code>newtype</code> package on Hackage:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a> ala <span class="dt">Ap</span> <span class="fu">foldMap</span> process inputs</span></code></pre></div>
<p>This solution is not convincing to me for a few reasons:</p>
<ul>
<li><p>It’s unergonomic in general</p>
<p>There are some places where <code>Ap</code> works just fine (such as in conjunction with <code>deriving via</code>), but typically using <code>Ap</code> directly within term-level code is a solution worse than the original problem; the newtype wrapping and unwrapping boilerplate more than counteracts the ergonomic improvements from using the <code>Semigroup</code> / <code>Monoid</code> instances.</p></li>
<li><p>In my view, there’s no downside to adding <code>Semigroup</code> and <code>Monoid</code> instances</p>
<p>… when only one law-abiding implementation of these instances is possible. See the caveat in the previous section.</p></li>
<li><p>This line of reasoning would eliminate many other useful instances</p>
<p>For example, one might remove the <code>Applicative</code> instance for list since it’s not the only possible instance and you could in theory always use a <code>newtype</code> to select the desired instance.</p></li>
</ul>
<h4 id="proof-of-laws">Proof of laws</h4>
<p>For completeness, I should also mention that the suggested <code>Semigroup</code> and <code>Monoid</code> instances are guaranteed to always be law-abiding instances. You can find the proof in Appendix B of my <a href="https://www.haskellforall.com/2014/07/equational-reasoning-at-scale.html">Equational reasoning at scale</a> post.</p>
</body>
</html>
Gabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.com1