aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarshall Lochbaum <mwlochbaum@gmail.com>2022-01-30 21:41:33 -0500
committerMarshall Lochbaum <mwlochbaum@gmail.com>2022-01-30 21:42:35 -0500
commitc208d37754ae81d9e67c80affa58c5ca8da95ee5 (patch)
tree3d8206cbc04a4bf61e81f382e1d57dedccc699a3
parenteb2325923fcac6970f578b9f970b15a41ebabd1c (diff)
Add partial documentation page on tacit programming (fixes #36)
-rw-r--r--README.md2
-rw-r--r--doc/README.md1
-rw-r--r--doc/glossary.md2
-rw-r--r--doc/identity.md2
-rw-r--r--doc/tacit.md44
-rw-r--r--docs/doc/glossary.html2
-rw-r--r--docs/doc/identity.html2
-rw-r--r--docs/doc/index.html1
-rw-r--r--docs/doc/tacit.html213
-rw-r--r--docs/index.html2
10 files changed, 265 insertions, 6 deletions
diff --git a/README.md b/README.md
index 4c3b0f70..070b3fd1 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ BQN aims to remove irregular and burdensome aspects of the APL tradition, and pu
It incorporates concepts developed over years of APL practice:
* With the [**leading axis model**](doc/leading.md), simpler primitives span the same functionality.
-* [Trains](doc/train.md) and combinators enable **tacit programming**.
+* [Trains](doc/train.md) and combinators enable [**tacit programming**](doc/tacit.md).
* Lightweight [**anonymous functions**](doc/block.md) (like [dfns](https://aplwiki.com/wiki/Dfn)) borrow some power from Lisp.
But it's redesigned from the ground up, with many features new to the array paradigm:
diff --git a/doc/README.md b/doc/README.md
index 891947ba..620d7c74 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -25,6 +25,7 @@ Concepts:
- [Array indices](indices.md)
- [Fill elements](fill.md)
- [The leading axis model](leading.md)
+- [Tacit programming](tacit.md)
- [Function trains](train.md)
- [Blocks](block.md) (including function and modifier definition)
- [Lexical scoping](lexical.md)
diff --git a/doc/glossary.md b/doc/glossary.md
index f274673e..fcbb4c72 100644
--- a/doc/glossary.md
+++ b/doc/glossary.md
@@ -130,4 +130,4 @@ The possible roles are:
* [**Header**](block.md#block-headers): A preface to a body in a block function or modifier indicating possible inputs, which is followed by a colon `:`.
* [**Label**](block.md#short-headers): A header consisting of a single name.
* **Body**: One sequence of statements in a block. Bodies, possibly preceded by headers, are separated by semicolons `;`.
-* **Tacit**: Code that defines functions or modifiers without using blocks.
+* [**Tacit**](tacit.md): Code that defines functions without using blocks.
diff --git a/doc/identity.md b/doc/identity.md
index fe9ad2e9..5876a885 100644
--- a/doc/identity.md
+++ b/doc/identity.md
@@ -44,7 +44,7 @@ Here `⊒` ends up being used as `π•Ž`. A similar case might be a function or p
## In tacit functions
-In a tacit context, `⊣` is roughly equivalent to `𝕨` and `⊒` to `𝕩`. In some (not too common) cases, it's even possible to translate a block function to tacit code directly by replacing the variables in this way.
+In a [tacit](tacit.md) context, `⊣` is roughly equivalent to `𝕨` and `⊒` to `𝕩`. In some (not too common) cases, it's even possible to translate a block function to tacit code directly by replacing the variables in this way.
3 {𝕩-𝕨÷1+𝕩} 5
3 (⊒-⊣÷1+⊒) 5
diff --git a/doc/tacit.md b/doc/tacit.md
new file mode 100644
index 00000000..8c67efaa
--- /dev/null
+++ b/doc/tacit.md
@@ -0,0 +1,44 @@
+*View this file with results and syntax highlighting [here](https://mlochbaum.github.io/BQN/doc/tacit.html).*
+
+# Tacit (point-free) programming
+
+[Tacit programming](https://en.wikipedia.org/wiki/Tacit_programming) ([APL Wiki](https://aplwiki.com/wiki/Tacit_programming)) is a general term used to refer to ways to define functions that don't refer to arguments directly (say, with identifiers). Instead, tacit programs are built up by combining smaller functions together; we'll discuss the ways BQN offers to combine functions on this page. Since primitive functions like those returning the left (`⊣`) and right (`⊒`) arguments, and selection functions (`βŠβŠ‘`), are available as building blocks, tacit programming doesn't keep the programmer from pinpointing a specific part of the input, as the description might lead you to believe. Nonetheless, it has its limitations. In larger tacit programs, moving values to the right place is tedious and error-prone because of the lack of a convenient labelling mechanism, and important context tends to disappear in a sea of symbols.
+
+In smaller amountsβ€”portions of a lineβ€”tacit programming can be the clearest way to express an idea, particularly when just one or two variables are used a few times. Consider the following three expressions to filter only the positive values from a list:
+
+ l ← 0β€Ώ5β€ΏΒ―2β€Ώ1β€ΏΒ―3β€ΏΒ―4
+
+ (0<l)/l
+ {(0<𝕩)/𝕩} l
+ 0⊸<⊸/ l
+
+The first of these expressions is the most direct, but with the variable name buried inside, it can't be used on an intermediate value and its input will have to be named. The other two forms stand alone as functions, so they can easily be placed anywhere in a program, even as an operand. But with even the small amount of structure added by a BQN anonymous function, the second method has more organization than action! The third, tacit, version strips away most of the organizing syntax to leave us with the essential pieces `0`, `<`, and `/` joined by combinators. The explicit function uses `𝕩` as a sort of pronoun ("I want the elements of it where it's greater than zero"), while the tacit one elides it ("give me the elements greater than zero").
+
+The ability to easily combine tacit and "explicit" programming such as statements or anonymous functions, far from being only a way to mitigate the disadvantages of these two methods, brings new advantages that no single paradigm could accomplish. Purely tacit programming *requires* programs to use *no* local variable names, but partly tacit programming *allows* them to use *fewer* names. That means names can be used only for the parts of a program that represent clean, human-understandable concepts. Another possible stategic choice is to use the fact that variables in a tacit expression are expanded as it's formed but those inside a block aren't. So `F←a⊸+` can be chosen to "freeze" the value of `a` in `F` without having to use an extra variable, while `F←{a+𝕩}` uses the current value of `a` each time `F` is called.
+
+The rest of this page describes BQN's tacit programming facilities. Deciding when to use them is a matter of taste, and experience.
+
+## Trains
+
+In modern APL and its relatives, the backbone of tacit infrastructure is the *function train*. Trains can take some practice to understand and use well, so they're described in more depth on [a dedicated page](train.md). The idea of trains is that you can "apply" a function to other functions, forming a composed function where it will actually apply to their results. So a typical use is to pair two functions as shown below: the pair `Β»β€ΏΒ«` is never formed, but the result of applying `T` is a pair.
+
+ T ← Β» β‹ˆ Β« # Pair both shift functions
+ T # Nothing happens yet...
+
+ T "abc" # Now it forms a pair
+
+ 'X' T "abc" # Each shift gets both arguments
+
+## Identity functions
+
+If you use trains even a little you'll quickly find the need to get an argument without applying any function to it. Take the pattern `{(𝕨F𝕩)G𝕨}` for example. You might expect `⊸⟜` (discussed below) to handle this, but they don't: in those combinators, the first function to be applied always has one argument, but `F` here has two. Instead, a good way to fit this into a tacit form is to note that `π•¨βŠ£π•©` is defined to be `𝕨`, and substitute backwards to give `{(𝕨F𝕩)G(π•¨βŠ£π•©)}`, which now has the form of a train `F G ⊣`.
+
+ "whatsin" {(π•¨βˆŠπ•©)/𝕨} "intersect"
+
+ "whatsin" (∊/⊣) "intersect"
+
+The functions `⊣⊒` are as simple as they come, but are discussed quite a bit on [their own page](identity.md). A definition is that `⊒` is `{𝕩}` and `⊣` is `{𝕩;𝕨}`, so that `⊒` returns its right argument, and `⊣` returns its left argument but will settle for the right one if there's just one.
+
+## Combinators
+
+<!--GEN combinator.bqn-->
diff --git a/docs/doc/glossary.html b/docs/doc/glossary.html
index 75048532..36482766 100644
--- a/docs/doc/glossary.html
+++ b/docs/doc/glossary.html
@@ -142,5 +142,5 @@
<li><a href="block.html#block-headers"><strong>Header</strong></a>: A preface to a body in a block function or modifier indicating possible inputs, which is followed by a colon <code><span class='Head'>:</span></code>.</li>
<li><a href="block.html#short-headers"><strong>Label</strong></a>: A header consisting of a single name.</li>
<li><strong>Body</strong>: One sequence of statements in a block. Bodies, possibly preceded by headers, are separated by semicolons <code><span class='Head'>;</span></code>.</li>
-<li><strong>Tacit</strong>: Code that defines functions or modifiers without using blocks.</li>
+<li><a href="tacit.html"><strong>Tacit</strong></a>: Code that defines functions without using blocks.</li>
</ul>
diff --git a/docs/doc/identity.html b/docs/doc/identity.html
index 3f4f624f..140b79b7 100644
--- a/docs/doc/identity.html
+++ b/docs/doc/identity.html
@@ -61,7 +61,7 @@
</pre>
<p>Here <code><span class='Function'>⊒</span></code> ends up being used as <code><span class='Function'>π•Ž</span></code>. A similar case might be a function or program with a caller-specified processing step. For example, a function to write some kind of file, with a parameter function to encrypt data before writing. To use no encryption, you'd pass a parameter <code><span class='Function'>⊒</span></code>. Or it might happen that you write a Choose (<code><span class='Modifier2'>β—Ά</span></code>) expression where one of the cases should do nothing <code><span class='Function'>⊒</span></code>, or return the left argument <code><span class='Function'>⊣</span></code>.</p>
<h2 id="in-tacit-functions"><a class="header" href="#in-tacit-functions">In tacit functions</a></h2>
-<p>In a tacit context, <code><span class='Function'>⊣</span></code> is roughly equivalent to <code><span class='Value'>𝕨</span></code> and <code><span class='Function'>⊒</span></code> to <code><span class='Value'>𝕩</span></code>. In some (not too common) cases, it's even possible to translate a block function to tacit code directly by replacing the variables in this way.</p>
+<p>In a <a href="tacit.html">tacit</a> context, <code><span class='Function'>⊣</span></code> is roughly equivalent to <code><span class='Value'>𝕨</span></code> and <code><span class='Function'>⊒</span></code> to <code><span class='Value'>𝕩</span></code>. In some (not too common) cases, it's even possible to translate a block function to tacit code directly by replacing the variables in this way.</p>
<a class="replLink" title="Open in the REPL" target="_blank" href="https://mlochbaum.github.io/BQN/try.html#code=MyB78J2VqS3wnZWow7cxK/Cdlal9IDUKMyAo4oqiLeKKo8O3MSviiqIpIDU=">↗️</a><pre> <span class='Number'>3</span> <span class='Brace'>{</span><span class='Value'>𝕩</span><span class='Function'>-</span><span class='Value'>𝕨</span><span class='Function'>Γ·</span><span class='Number'>1</span><span class='Function'>+</span><span class='Value'>𝕩</span><span class='Brace'>}</span> <span class='Number'>5</span>
4.5
<span class='Number'>3</span> <span class='Paren'>(</span><span class='Function'>⊒-⊣÷</span><span class='Number'>1</span><span class='Function'>+⊒</span><span class='Paren'>)</span> <span class='Number'>5</span>
diff --git a/docs/doc/index.html b/docs/doc/index.html
index 98587366..b4110034 100644
--- a/docs/doc/index.html
+++ b/docs/doc/index.html
@@ -30,6 +30,7 @@
<li><a href="indices.html">Array indices</a></li>
<li><a href="fill.html">Fill elements</a></li>
<li><a href="leading.html">The leading axis model</a></li>
+<li><a href="tacit.html">Tacit programming</a></li>
<li><a href="train.html">Function trains</a></li>
<li><a href="block.html">Blocks</a> (including function and modifier definition)</li>
<li><a href="lexical.html">Lexical scoping</a></li>
diff --git a/docs/doc/tacit.html b/docs/doc/tacit.html
new file mode 100644
index 00000000..b65bac49
--- /dev/null
+++ b/docs/doc/tacit.html
@@ -0,0 +1,213 @@
+<head>
+ <link href="../favicon.ico" rel="shortcut icon" type="image/x-icon"/>
+ <link href="../style.css" rel="stylesheet"/>
+ <title>BQN: Tacit (point-free) programming</title>
+</head>
+<div class="nav">(<a href="https://github.com/mlochbaum/BQN">github</a>) / <a href="../index.html">BQN</a> / <a href="index.html">doc</a></div>
+<h1 id="tacit-point-free-programming"><a class="header" href="#tacit-point-free-programming">Tacit (point-free) programming</a></h1>
+<p><a href="https://en.wikipedia.org/wiki/Tacit_programming">Tacit programming</a> (<a href="https://aplwiki.com/wiki/Tacit_programming">APL Wiki</a>) is a general term used to refer to ways to define functions that don't refer to arguments directly (say, with identifiers). Instead, tacit programs are built up by combining smaller functions together; we'll discuss the ways BQN offers to combine functions on this page. Since primitive functions like those returning the left (<code><span class='Function'>⊣</span></code>) and right (<code><span class='Function'>⊒</span></code>) arguments, and selection functions (<code><span class='Function'>βŠβŠ‘</span></code>), are available as building blocks, tacit programming doesn't keep the programmer from pinpointing a specific part of the input, as the description might lead you to believe. Nonetheless, it has its limitations. In larger tacit programs, moving values to the right place is tedious and error-prone because of the lack of a convenient labelling mechanism, and important context tends to disappear in a sea of symbols.</p>
+<p>In smaller amountsβ€”portions of a lineβ€”tacit programming can be the clearest way to express an idea, particularly when just one or two variables are used a few times. Consider the following three expressions to filter only the positive values from a list:</p>
+<a class="replLink" title="Open in the REPL" target="_blank" href="https://mlochbaum.github.io/BQN/try.html#code=bCDihpAgMOKAvzXigL/CrzLigL8x4oC/wq8z4oC/wq80CgooMDxsKS9sCnsoMDzwnZWpKS/wnZWpfSBsCjDiirg84oq4LyBs">↗️</a><pre> <span class='Value'>l</span> <span class='Gets'>←</span> <span class='Number'>0</span><span class='Ligature'>β€Ώ</span><span class='Number'>5</span><span class='Ligature'>β€Ώ</span><span class='Number'>Β―2</span><span class='Ligature'>β€Ώ</span><span class='Number'>1</span><span class='Ligature'>β€Ώ</span><span class='Number'>Β―3</span><span class='Ligature'>β€Ώ</span><span class='Number'>Β―4</span>
+
+ <span class='Paren'>(</span><span class='Number'>0</span><span class='Function'>&lt;</span><span class='Value'>l</span><span class='Paren'>)</span><span class='Function'>/</span><span class='Value'>l</span>
+⟨ 5 1 ⟩
+ <span class='Brace'>{</span><span class='Paren'>(</span><span class='Number'>0</span><span class='Function'>&lt;</span><span class='Value'>𝕩</span><span class='Paren'>)</span><span class='Function'>/</span><span class='Value'>𝕩</span><span class='Brace'>}</span> <span class='Value'>l</span>
+⟨ 5 1 ⟩
+ <span class='Number'>0</span><span class='Modifier2'>⊸</span><span class='Function'>&lt;</span><span class='Modifier2'>⊸</span><span class='Function'>/</span> <span class='Value'>l</span>
+⟨ 5 1 ⟩
+</pre>
+<p>The first of these expressions is the most direct, but with the variable name buried inside, it can't be used on an intermediate value and its input will have to be named. The other two forms stand alone as functions, so they can easily be placed anywhere in a program, even as an operand. But with even the small amount of structure added by a BQN anonymous function, the second method has more organization than action! The third, tacit, version strips away most of the organizing syntax to leave us with the essential pieces <code><span class='Number'>0</span></code>, <code><span class='Function'>&lt;</span></code>, and <code><span class='Function'>/</span></code> joined by combinators. The explicit function uses <code><span class='Value'>𝕩</span></code> as a sort of pronoun (&quot;I want the elements of it where it's greater than zero&quot;), while the tacit one elides it (&quot;give me the elements greater than zero&quot;).</p>
+<p>The ability to easily combine tacit and &quot;explicit&quot; programming such as statements or anonymous functions, far from being only a way to mitigate the disadvantages of these two methods, brings new advantages that no single paradigm could accomplish. Purely tacit programming <em>requires</em> programs to use <em>no</em> local variable names, but partly tacit programming <em>allows</em> them to use <em>fewer</em> names. That means names can be used only for the parts of a program that represent clean, human-understandable concepts. Another possible stategic choice is to use the fact that variables in a tacit expression are expanded as it's formed but those inside a block aren't. So <code><span class='Function'>F</span><span class='Gets'>←</span><span class='Value'>a</span><span class='Modifier2'>⊸</span><span class='Function'>+</span></code> can be chosen to &quot;freeze&quot; the value of <code><span class='Value'>a</span></code> in <code><span class='Function'>F</span></code> without having to use an extra variable, while <code><span class='Function'>F</span><span class='Gets'>←</span><span class='Brace'>{</span><span class='Value'>a</span><span class='Function'>+</span><span class='Value'>𝕩</span><span class='Brace'>}</span></code> uses the current value of <code><span class='Value'>a</span></code> each time <code><span class='Function'>F</span></code> is called.</p>
+<p>The rest of this page describes BQN's tacit programming facilities. Deciding when to use them is a matter of taste, and experience.</p>
+<h2 id="trains"><a class="header" href="#trains">Trains</a></h2>
+<p>In modern APL and its relatives, the backbone of tacit infrastructure is the <em>function train</em>. Trains can take some practice to understand and use well, so they're described in more depth on <a href="train.html">a dedicated page</a>. The idea of trains is that you can &quot;apply&quot; a function to other functions, forming a composed function where it will actually apply to their results. So a typical use is to pair two functions as shown below: the pair <code><span class='Function'>Β»</span><span class='Ligature'>β€Ώ</span><span class='Function'>Β«</span></code> is never formed, but the result of applying <code><span class='Function'>T</span></code> is a pair.</p>
+<a class="replLink" title="Open in the REPL" target="_blank" href="https://mlochbaum.github.io/BQN/try.html#code=VCDihpAgwrsg4ouIIMKrICAgICMgUGFpciBib3RoIHNoaWZ0IGZ1bmN0aW9ucwpUICAgICAgICAgICAgIyBOb3RoaW5nIGhhcHBlbnMgeWV0Li4uCgpUICJhYmMiICAgICAgIyBOb3cgaXQgZm9ybXMgYSBwYWlyCgonWCcgVCAiYWJjIiAgIyBFYWNoIHNoaWZ0IGdldHMgYm90aCBhcmd1bWVudHM=">↗️</a><pre> <span class='Function'>T</span> <span class='Gets'>←</span> <span class='Function'>Β»</span> <span class='Function'>β‹ˆ</span> <span class='Function'>Β«</span> <span class='Comment'># Pair both shift functions
+</span> <span class='Function'>T</span> <span class='Comment'># Nothing happens yet...
+</span>Β»β‹ˆΒ«
+
+ <span class='Function'>T</span> <span class='String'>&quot;abc&quot;</span> <span class='Comment'># Now it forms a pair
+</span>⟨ " ab" "bc " ⟩
+
+ <span class='String'>'X'</span> <span class='Function'>T</span> <span class='String'>&quot;abc&quot;</span> <span class='Comment'># Each shift gets both arguments
+</span>⟨ "Xab" "bcX" ⟩
+</pre>
+<h2 id="identity-functions"><a class="header" href="#identity-functions">Identity functions</a></h2>
+<p>If you use trains even a little you'll quickly find the need to get an argument without applying any function to it. Take the pattern <code><span class='Brace'>{</span><span class='Paren'>(</span><span class='Value'>𝕨</span><span class='Function'>F</span><span class='Value'>𝕩</span><span class='Paren'>)</span><span class='Function'>G</span><span class='Value'>𝕨</span><span class='Brace'>}</span></code> for example. You might expect <code><span class='Modifier2'>⊸⟜</span></code> (discussed below) to handle this, but they don't: in those combinators, the first function to be applied always has one argument, but <code><span class='Function'>F</span></code> here has two. Instead, a good way to fit this into a tacit form is to note that <code><span class='Value'>𝕨</span><span class='Function'>⊣</span><span class='Value'>𝕩</span></code> is defined to be <code><span class='Value'>𝕨</span></code>, and substitute backwards to give <code><span class='Brace'>{</span><span class='Paren'>(</span><span class='Value'>𝕨</span><span class='Function'>F</span><span class='Value'>𝕩</span><span class='Paren'>)</span><span class='Function'>G</span><span class='Paren'>(</span><span class='Value'>𝕨</span><span class='Function'>⊣</span><span class='Value'>𝕩</span><span class='Paren'>)</span><span class='Brace'>}</span></code>, which now has the form of a train <code><span class='Function'>F</span> <span class='Function'>G</span> <span class='Function'>⊣</span></code>.</p>
+<a class="replLink" title="Open in the REPL" target="_blank" href="https://mlochbaum.github.io/BQN/try.html#code=IndoYXRzaW4iIHso8J2VqOKIivCdlakpL/Cdlah9ICJpbnRlcnNlY3QiCgoid2hhdHNpbiIgKOKIii/iiqMpICJpbnRlcnNlY3Qi">↗️</a><pre> <span class='String'>&quot;whatsin&quot;</span> <span class='Brace'>{</span><span class='Paren'>(</span><span class='Value'>𝕨</span><span class='Function'>∊</span><span class='Value'>𝕩</span><span class='Paren'>)</span><span class='Function'>/</span><span class='Value'>𝕨</span><span class='Brace'>}</span> <span class='String'>&quot;intersect&quot;</span>
+"tsin"
+
+ <span class='String'>&quot;whatsin&quot;</span> <span class='Paren'>(</span><span class='Function'>∊/⊣</span><span class='Paren'>)</span> <span class='String'>&quot;intersect&quot;</span>
+"tsin"
+</pre>
+<p>The functions <code><span class='Function'>⊣⊒</span></code> are as simple as they come, but are discussed quite a bit on <a href="identity.html">their own page</a>. A definition is that <code><span class='Function'>⊒</span></code> is <code><span class='Brace'>{</span><span class='Value'>𝕩</span><span class='Brace'>}</span></code> and <code><span class='Function'>⊣</span></code> is <code><span class='Brace'>{</span><span class='Value'>𝕩</span><span class='Head'>;</span><span class='Value'>𝕨</span><span class='Brace'>}</span></code>, so that <code><span class='Function'>⊒</span></code> returns its right argument, and <code><span class='Function'>⊣</span></code> returns its left argument but will settle for the right one if there's just one.</p>
+<h2 id="combinators"><a class="header" href="#combinators">Combinators</a></h2>
+<svg viewBox='0 0 850 530'>
+ <g font-size='20px' text-anchor='middle' transform='translate(145,20)'>
+ <rect class='code' stroke-width='1' rx='12' x='-120.4' y='1' width='240.8' height='205'/>
+ <text dy='0.32em' y='223' fill='currentColor'>Atop</text>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(-60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>∘</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L0 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 57L0 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='0' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='0' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕨</tspan> <tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>∘</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L0 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 57L-32 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 57L32 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='114'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='0' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='-32' y='114'><tspan class='Value'>𝕨</tspan></text>
+ <text dy='0.32em' x='32' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ </g>
+ <g font-size='20px' text-anchor='middle' transform='translate(425,20)'>
+ <rect class='code' stroke-width='1' rx='12' x='-120.4' y='1' width='240.8' height='205'/>
+ <text dy='0.32em' y='223' fill='currentColor'>Over</text>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(-60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>β—‹</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L0 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 57L0 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='0' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='0' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕨</tspan> <tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>β—‹</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L-32 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M-32 57L-32 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L32 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M32 57L32 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='114'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='-32' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='32' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='-32' y='114'><tspan class='Value'>𝕨</tspan></text>
+ <text dy='0.32em' x='32' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ </g>
+ <g font-size='20px' text-anchor='middle' transform='translate(705,20)'>
+ <rect class='code' stroke-width='1' rx='12' x='-120.4' y='1' width='240.8' height='205'/>
+ <text dy='0.32em' y='223' fill='currentColor'>Constant</text>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(-60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕗</tspan><tspan class='Modifier'>Λ™</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L0 57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='114'/>
+ <text dy='0.32em' x='0' y='57'><tspan class='Value'>𝕗</tspan></text>
+ <text dy='0.32em' x='0' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕨</tspan> <tspan class='Value'>𝕗</tspan><tspan class='Modifier'>Λ™</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L0 57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='114'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='114'/>
+ <text dy='0.32em' x='0' y='57'><tspan class='Value'>𝕗</tspan></text>
+ <text dy='0.32em' x='-32' y='114'><tspan class='Value'>𝕨</tspan></text>
+ <text dy='0.32em' x='32' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ </g>
+ <g font-size='20px' text-anchor='middle' transform='translate(145,280)'>
+ <rect class='code' stroke-width='1' rx='12' x='-120.4' y='1' width='240.8' height='205'/>
+ <text dy='0.32em' y='223' fill='currentColor'>Before</text>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(-60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>⊸</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L-32 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M-32 57L0 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0Q41.6 57 0 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='-32' y='57'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='0' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕨</tspan> <tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>⊸</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L-32 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M-32 57L-32 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0C40 57 32 51.3 32 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='114'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='-32' y='57'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='-32' y='114'><tspan class='Value'>𝕨</tspan></text>
+ <text dy='0.32em' x='32' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ </g>
+ <g font-size='20px' text-anchor='middle' transform='translate(425,280)'>
+ <rect class='code' stroke-width='1' rx='12' x='-120.4' y='1' width='240.8' height='205'/>
+ <text dy='0.32em' y='223' fill='currentColor'>After</text>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(-60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>⟜</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0Q-41.6 57 0 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L32 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M32 57L0 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='32' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='0' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕨</tspan> <tspan class='Function'>𝔽</tspan><tspan class='Modifier2'>⟜</tspan><tspan class='Function'>𝔾</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0C-40 57 -32 51.3 -32 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0L32 57'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M32 57L32 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='57'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='114'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='32' y='57'><tspan class='Function'>𝔾</tspan></text>
+ <text dy='0.32em' x='-32' y='114'><tspan class='Value'>𝕨</tspan></text>
+ <text dy='0.32em' x='32' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ </g>
+ <g font-size='20px' text-anchor='middle' transform='translate(705,280)'>
+ <rect class='code' stroke-width='1' rx='12' x='-120.4' y='1' width='240.8' height='205'/>
+ <text dy='0.32em' y='223' fill='currentColor'>Self/Swap</text>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(-60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Function'>𝔽</tspan><tspan class='Modifier'>˜</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0Q-41.6 57 0 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0Q41.6 57 0 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='0' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ <g font-size='21px' font-family='BQN,monospace' transform='translate(60.87,25)'>
+ <text dy='0.32em' y='155' font-size='19px'><tspan class='Value'>𝕨</tspan> <tspan class='Function'>𝔽</tspan><tspan class='Modifier'>˜</tspan> <tspan class='Value'>𝕩</tspan></text>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0C-40 28.5 0 57 32 114'/>
+ <path class='yellow' style='fill:none' stroke-width='2' d='M0 0C40 28.5 0 57 -32 114'/>
+ <circle r='12' class='code' stroke-width='0' cx='0' cy='0'/>
+ <circle r='12' class='code' stroke-width='0' cx='-32' cy='114'/>
+ <circle r='12' class='code' stroke-width='0' cx='32' cy='114'/>
+ <text dy='0.32em' x='0' y='0'><tspan class='Function'>𝔽</tspan></text>
+ <text dy='0.32em' x='-32' y='114'><tspan class='Value'>𝕨</tspan></text>
+ <text dy='0.32em' x='32' y='114'><tspan class='Value'>𝕩</tspan></text>
+ </g>
+ </g>
+</svg>
+
diff --git a/docs/index.html b/docs/index.html
index 6aa53d48..908d912e 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -48,7 +48,7 @@
<p>It incorporates concepts developed over years of APL practice:</p>
<ul>
<li>With the <a href="doc/leading.html"><strong>leading axis model</strong></a>, simpler primitives span the same functionality.</li>
-<li><a href="doc/train.html">Trains</a> and combinators enable <strong>tacit programming</strong>.</li>
+<li><a href="doc/train.html">Trains</a> and combinators enable <a href="doc/tacit.html"><strong>tacit programming</strong></a>.</li>
<li>Lightweight <a href="doc/block.html"><strong>anonymous functions</strong></a> (like <a href="https://aplwiki.com/wiki/Dfn">dfns</a>) borrow some power from Lisp.</li>
</ul>
<p>But it's redesigned from the ground up, with many features new to the array paradigm:</p>