From df6d6a0fa85c07c67eaa40a097953e3290f5d356 Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Wed, 7 Jul 2021 19:59:57 -0400 Subject: Continued editing and links --- docs/doc/logic.html | 13 +++++++------ docs/doc/map.html | 6 +++--- docs/doc/match.html | 17 +++++++++++------ docs/doc/namespace.html | 2 +- docs/doc/oop.html | 6 +++++- docs/doc/order.html | 8 ++++---- docs/doc/prefixes.html | 12 ++++++------ docs/doc/range.html | 6 +++--- docs/doc/replicate.html | 8 ++++---- 9 files changed, 44 insertions(+), 34 deletions(-) (limited to 'docs') diff --git a/docs/doc/logic.html b/docs/doc/logic.html index fb679153..2f7d6c0f 100644 --- a/docs/doc/logic.html +++ b/docs/doc/logic.html @@ -8,6 +8,7 @@

BQN retains the APL symbols and for logical and and or, and changed APL's ~ to ¬ for not, since ~ looks too much like ˜ and ¬ is more common in mathematics today. Like J, BQN extends Not to the linear function 1-. However, it discards GCD and LCM as extensions of And and Or, and instead uses bilinear extensions: And is identical to Times (×), while Or is ׬, following De Morgan's laws (other ways of obtaining a function for Or give an equivalent result—there is only one bilinear extension).

If the arguments are probabilities of independent events, then an extended function gives the probability of the boolean function on their outcomes (for example, if A occurs with probability a and B with probability b independent of A, then A or B occurs with probability ab). These extensions have also been used in complexity theory, because they allow mathematicians to transfer a logical circuit from the discrete to the continuous domain in order to use calculus on it.

Both valences of ¬ are equivalent to the fork 1+-. The dyadic valence, called "Span", computes the number of integers in the range from 𝕩 to 𝕨, inclusive, when both arguments are integers and 𝕩𝕨 (note the reversed order, which is used for consistency with subtraction). This function has many uses, and in particular is relevant to the Windows function.

+

These functions are considered arithmetic functions and thus are pervasive.

Definitions

We define:

Not  1+-  # also Span
@@ -18,7 +19,7 @@
 
Or   +-×
 

Examples

-

We can form truth tables including the non-integer value one-half:

+

We can form truth tables including the non-integer value one-half:

↗️
    ¬ 00.51
 ⟨ 1 0.5 0 ⟩
 
@@ -36,11 +37,11 @@
     1    1 1  
              ┘
 
-

As with logical And and Or, any value and 0 is 0, while any value or 1 is 1. The other boolean values give the identity elements for the two functions: 1 and any value gives that value, as does 0 or the value.

+

As with logical And and Or, any value and 0 is 0, while any value or 1 is 1. The other boolean values give the identity values for the two functions: 1 and any value gives that value, as does 0 or the value.

Why not GCD and LCM?

The main reason for omitting these functions is that they are complicated and, when applied to real or complex numbers, require a significant number of design decisions where there is no obvious choice (for example, whether to use comparison tolerance). On the other hand, these functions are fairly easy to implement, which allows the programmer to control the details, and also add functionality such as the extended GCD.

-

A secondary reason is that the GCD falls short as an extension of Or, because its identity element 0 is not total. 0x, for a real number x, is actually equal to |x and not x: for example, 0¯2 is 2 in APL. This means the identity 0x ←→ x isn't reliable in APL.

-

Identity elements

-

It's common to apply ´ or ´ to a list (checking whether all elements are true and whether any are true, respectively), and so it's important for extensions to And and Or to share their identity element. Minimum and Maximum do match And and Or when restricted to booleans, but they have different identity elements. It would be dangerous to use Maximum to check whether any element of a list is true because >⌈´⟨⟩ yields ¯∞ instead of 0—a bug waiting to happen. Always using 0 as a left argument to ´ fixes this problem but requires more work from the programmer, making errors more likely.

-

It is easy to prove that the bilinear extensions have the identity elements we want. Of course 1x is 1×x, or x, and 0x is 0׬x, or ¬1׬x, giving ¬¬x or x again. Both functions are commutative, so these identities are double-sided.

+

A secondary reason is that the GCD falls short as an extension of Or, because its identity value 0 is not total. 0x, for a real number x, is actually equal to |x and not x: for example, 0¯2 is 2 in APL. This means the identity 0x ←→ x isn't reliable in APL.

+

Identity values

+

It's common to apply a fold ´ or ´ to a list (checking whether all elements are true and whether any are true, respectively), and so it's important for extensions to And and Or to share their identity value. Minimum and Maximum do match And and Or when restricted to booleans, but they have different identity values. It would be dangerous to use Maximum to check whether any element of a list is true because ´⟨⟩ yields ¯∞ instead of 0—a bug waiting to happen. To avoid this the programmer would have to use an initial value 𝕨 of 0, which is easy to forget.

+

It's not hard to prove that the bilinear extensions have the identity values we want. Of course 1x is 1×x, or x, and 0x is 0׬x, or ¬1׬x, giving ¬¬x or x again. Both functions are commutative, so these values are identities on the right as well.

Other logical identities do not necessarily hold. For example, in boolean logic And distributes over Or and vice-versa: abc ←→ (ab)(ac). But substituting × for and +-× for we find that the left hand side is (a×b)+(a×c)+(a×b×c) while the right gives (a×b)+(a×c)+(a×b×a×c). These are equivalent for arbitrary b and c only if a=a×a, that is, a is 0 or 1. In terms of probabilities the difference when a is not boolean is caused by failure of independence. On the left hand side, the two arguments of every logical function are independent. On the right hand side, each pair of arguments to are independent, but the two arguments to , ab and ac, are not. The relationship between these arguments means that logical equivalences no longer apply.

diff --git a/docs/doc/map.html b/docs/doc/map.html index ae5491ee..f228e3df 100644 --- a/docs/doc/map.html +++ b/docs/doc/map.html @@ -65,7 +65,7 @@ ↗️
    o⟨⟩  {o<𝕩}¨ "index""order"  o
 "indexorder"
 
-

When an array is displayed, index order is the same as the top-to-bottom, left-to-right reading order of English. It's also the same as the ordering of Deshape's result, so that here o ends up being 𝕩. The dyadic cases described in the following sections will also have a defined evaluation order, but it's not easy to describe it in terms of the arguments: instead, the result elements are produced in index order.

+

When an array is displayed, index order is the same as the top-to-bottom, left-to-right reading order of English. It's also the same as the ordering of Deshape's result, so that here o ends up being 𝕩. The dyadic cases described in the following sections will also have a defined evaluation order, but it's not as easy to describe it in terms of the arguments: instead, the result elements are produced in index order.

Table

@@ -217,11 +217,11 @@ ↗️
    00  "ABCD"  "0123"
 ⟨ "A0" "B1" "C2" "D3" ⟩
 
-

If the argument lengths don't match then Each gives an error. This contrasts with zip in many languages, which drops elements from the longer argument. This is rarely wanted in BQN, and having an error right away saves debugging time.

+

If the argument lengths don't match then Each gives an error. This contrasts with zip in many languages, which drops elements from the longer argument (this is natural for linked lists). This flexibility is rarely wanted in BQN, and having an error right away saves debugging time.

↗️
    "ABC" ¨ "01234"
 ERROR
 
-

Arguments can have any shape as long as the axis lengths match up. As with Table, the result elements don't depend on this shape but the result shape does.

+

Arguments can have any shape as long as the axis lengths match up. As with Table, the result elements don't depend on these shapes but the result shape does.

↗️
    (>203010,504060) +¨ 210321
 ┌─                               
 ╵ ⟨ 20 21 ⟩    ⟨ 30 ⟩    ⟨⟩      
diff --git a/docs/doc/match.html b/docs/doc/match.html
index 41c2a45e..03da6ad6 100644
--- a/docs/doc/match.html
+++ b/docs/doc/match.html
@@ -11,7 +11,7 @@
     4  <4
 1
 
-

Match always gives the same result as Equals (=) when both arguments are atoms, but the two functions are extended to arrays differently: while Equals maps over array arguments to return an array of results, Match compares them in totality and always returns one boolean (it never gives an error). Match is the basis for BQN's search and self-comparison functions.

+

Match always gives the same result as Equals (=) when both arguments are atoms, but the two functions are extended to arrays differently: while the pervasive Equals maps over array arguments to return an array of results, Match compares them in totality and always returns one boolean (it never gives an error). Match is the basis for BQN's search and self-comparison functions.

↗️
    "abc" = "acc"
 ⟨ 1 0 1 ⟩
     "abc"  "acc"
@@ -22,7 +22,7 @@
     "abc"  "ab"
 0
 
-

Match compares arrays based on their fundamental properties—shape and elements—and not the fill element, which is an inferred property. Since it can be computed differently in different implementations, using the fill element in Match could lead to some confusing results. Even if the implementation doesn't define a fill for 'a''b''c', it should still be considered to match "abc".

+

Match compares arrays based on their fundamental properties—shape and elements—and not the fill element, which is an inferred property. Since it can be computed differently in different implementations, using the fill element in Match could lead to some confusing results. Even if the implementation doesn't define a fill for 'a''b''c', it should still be considered to match "abc".

To give a precise definition, two arrays are considered to match if they have the same shape and all corresponding elements from the two arrays match. Every array has a finite depth so this recursive definition always ends up comparing non-arrays, or atoms. An array never matches an atom, so the result if only one argument is an atom is 0. The interesting case is when both arguments are atoms, discussed below.

Atomic equality

Atoms in BQN have six possible types: number, character, function, 1-modifier, 2-modifier, and namespace. Equality is not allowed to fail for any two arguments, so it needs to be defined on all of these types.

@@ -31,8 +31,9 @@ ⟨ 0 0 0 ⟩

Two characters are equal when they have the same code point. Numeric equality depends on the number system in use, but probably works about how you expect. If you're coming from APL, note that current BQN implementations don't employ comparison tolerance. To see if two floats are roughly equal you'll need to write a tolerant comparison yourself, but how often do you really need to do this?

-↗️
    'x' = "wxyz"
+↗️
    'x' = "wxyz"
 ⟨ 0 1 0 0 ⟩
+
     1.25 = 1 + 0.25
 1
 
@@ -43,18 +44,21 @@
  • Block instances or namespaces are equal if they are the same instance.
  • The first two are fairly similar to how numbers and arrays work. Primitives and compounds like trains, or modifiers with bound operands, are immutable, so they are defined purely by what components they contain.

    -↗️
        +,-,× = +,-,÷
    +↗️
        +,-,× = +,-,÷
     ⟨ 1 1 0 ⟩
    +
         + - × = + - ÷  # Compare two three-trains component-wise
     ⟨ 0 ⟩
    +
         + - ÷ = + - ÷
     ⟨ 1 ⟩
     

    This approach can't tell you whether two functions are mathematically different—that is, whether they ever return different results given the same arguments (this is an undecidable problem, and also gets confusing since "different" is included in its own definition). However, if two functions compare equal, then they will always return the same results.

    Block equality

    The final point above about block instances is subtler. An instance of a block function or modifier is mutable, meaning that its behavior can change over the course of a program. Consider the following two functions:

    -↗️
        FG  { a10  {a+𝕩}{a𝕩} }
    +↗️
        FG  { a10  {a+𝕩}{a𝕩} }
     ⟨ *function* *function* ⟩
    +
         F 5   # One result
     15
         G 8
    @@ -63,9 +67,10 @@
     13
     

    (A side note is that BQN restricts what can cause these side effects: they can only happen by calling a block function or modifier, and never a primitive or purely tacit operation). Now suppose we share the value of F with another variable. When we apply G, the result of F might change, but so does F1! This effect is called aliasing.

    -↗️
        F1  F
    +↗️
        F1  F
         {𝕏 6}¨ FF1
     ⟨ 14 14 ⟩
    +
         G 3
     3
         {𝕏 6}¨ FF1
    diff --git a/docs/doc/namespace.html b/docs/doc/namespace.html
    index ad38de4e..b8332eae 100644
    --- a/docs/doc/namespace.html
    +++ b/docs/doc/namespace.html
    @@ -21,7 +21,7 @@
     

    The features of namespaces that make them useful in BQN programming are encapsulation and mutability. But these are exactly the same features that closures provide! In fact a namespace is not much more than a closure with a name lookup system. Consequently namespaces don't really expand the basic functionality of the language, but just make it easier to use.

    Namespaces improve encapsulation by allowing many values to be exported at once. With only one way to call them, functions and modifiers aren't such a good way to define a large part of a program. With a namespace you can define lots of things and expose exactly the ones you want to the rest of the world. For example, it's typical for files to define namespaces. A reader can see the exported values just by searching for , and if you're nice, you might declare them all at the beginning of the file. Careful use of exports can guarantee that potentially dangerous functions are used correctly: if it's only valid to call function B after function A has been called, export AB{A𝕩B𝕩} and don't export B.

    Mutability means that the behavior of one namespace can change over the course of the program. Mutability is often a liability, so make sure you really need it before leaning too heavily on this property. While there's no way to tell from the outside that a particular namespace is mutable, you can tell it isn't if the source code doesn't contain , as this is the only way it can modify the variables it contains.

    -

    A namespace that makes use of mutability is essentially an object: a collection of state along with operations that act on it. Object-oriented programming is the other major use of namespaces. Contrary to the name, there's never a need to orient your programming around objects, and it's perfectly fine to use an object here or there when you need to, for instance to build a mutable queue of values.

    +

    A namespace that makes use of mutability is essentially an object: a collection of state along with operations that act on it. Object-oriented programming is the other major use of namespaces. Contrary to the name, there's never a need to orient your programming around objects, and it's perfectly fine to use an object here or there when you need to, for instance to build a mutable queue of values.

    Exports

    The double arrow is used to export variables from a block or file, making the result a namespace instead of the result of the last line. There are two ways to export variables. First, in the variable definition can be replaced with to export the variable as it's defined. Second, an export statement consisting of an assignment target followed by , with nothing to the right, exports the variables in the target and does nothing else. These export statements can be placed anywhere in the relevant program or body, including before declaration or on the last line, and a given variable can be exported any number of times. The block in the example below has two statements that export variables, exporting a, b, and c.

    example  {
    diff --git a/docs/doc/oop.html b/docs/doc/oop.html
    index c2c597db..6a0c339d 100644
    --- a/docs/doc/oop.html
    +++ b/docs/doc/oop.html
    @@ -87,12 +87,16 @@
     
        t  towerOfHanoi
         t.View@
       0 1 2 3 4  ⟨⟩ ⟨⟩ 
    +
         t.Move 02
       1 2 3 4  ⟨⟩  0  
    +
         t.Move 12
     ! "No disk to move"
    +
         t.Move 01
       2 3 4   1   0  
    +
         t.Move 21
       2 3 4   0 1  ⟨⟩ 
     
    @@ -114,7 +118,7 @@

    A stack is a particularly simple class to make because its state can be represented efficiently as a BQN value. Other data structures don't allow this, and will often require an extra Node class when they are implemented—see MakeQueue below.

    Mutability

    -

    An object is one way to transform variable mutation into mutable data. These are two different concepts: changes which value is attached to a name in a scope, while mutable data means that the behavior of a particular value can change. But if a value is linked to a scope (for an object, the scope that contains its fields), then variable mutation in that scope can chang the value's behavior. In fact, in BQN this is the only way to create mutable data. Which doesn't mean it's rare: functions, modifiers, and namespaces are all potentially mutable. The difference between objects and the operations is just a matter of syntax. Mutability in operations can only be observed by calling them. For instance F 10 or -_m could return a different result even if the variables involved don't change value. Mutability in an object can be observed by accessing a member, meaning that obj.field or fieldobj can yield different values over the course of a program even if obj is still the same object.

    +

    An object is one way to transform variable mutation into mutable data. These are two different concepts: changes which value is attached to a name in a scope, while mutable data means that the behavior of a particular value can change. But if a value is linked to a scope (for an object, the scope that contains its fields), then variable mutation in that scope can change the value's behavior. In fact, in BQN this is the only way to create mutable data. Which doesn't mean it's rare: functions, modifiers, and namespaces are all potentially mutable. The difference between objects and the operations is just a matter of syntax. Mutability in operations can only be observed by calling them. For instance F 10 or -_m could return a different result even if the variables involved don't change value. Mutability in an object can only be observed by accessing a member, meaning that obj.field or fieldobj can yield different values over the course of a program even if obj is still the same object.

    Let's look at how mutability plays out in an example class for a single-ended queue. This queue works by linking new nodes to the tail t of the queue, and detaching nodes from the head h when requested (a detached node will still point to h, but nothing in the queue points to it, so it's unreachable and will eventually be garbage collected). Each node has some data v and a single node reference n directed tailwards; in a double-ended queue or more complicated structure it would have more references. You can find every mutable variable in the queue by searching for , which shows that t and h in the queue, and n in a node, may be mutated. It's impossible for the other variables to change value once they're assigned.

    MakeQueue  {𝕤
       the{SetN{h𝕩}}
    diff --git a/docs/doc/order.html b/docs/doc/order.html
    index e9e5931c..24db519a 100644
    --- a/docs/doc/order.html
    +++ b/docs/doc/order.html
    @@ -5,7 +5,7 @@
     
     
     

    Ordering functions

    -

    BQN has six functions that order arrays as part of their operation (the comparison functions ≤<>≥ only order atoms, so they aren't included). These come in three pairs, where one of each pair uses an ascending ordering and the other uses a descending ordering.

    +

    BQN has six functions that order arrays as part of their operation (the comparison functions ≤<>≥ only order atoms, so they aren't included). These come in three pairs, where one of each pair uses an ascending ordering and the other uses a descending ordering.

    • ∨∧, Sort, rearranges the argument to order it
    • ⍒⍋, Grade, outputs the permutation that Sort would use to rearrange it
    • @@ -21,7 +21,7 @@ "δαβγ" "δγβα"
    -

    Sort Down always matches Sort Up reversed, . The reason for this is that BQN's array ordering is a total order, meaning that if one array doesn't come earlier or later that another array in the ordering then the two arrays match. Since any two non-matching argument cells are strictly ordered, they will have one ordering in and the opposite ordering in . With the reverse, any pair of non-matching cells are ordered the same way in and . Since these two results have the same major cells in the same order, they match. However, note that the results will not always behave identically because Match doesn't take fill elements into account (if you're curious, take a look at ¨0,"" versus ¨0,"").

    +

    Sort Down always matches Sort Up reversed, . The reason for this is that BQN's array ordering is a total order, meaning that if one array doesn't come earlier or later that another array in the ordering then the two arrays match. Since any two non-matching argument cells are strictly ordered, they will have one ordering in and the opposite ordering in . With the reverse, any pair of non-matching cells are ordered the same way in and . Since these two results have the same major cells in the same order, they match. However, note that the results will not always behave identically because Match doesn't take fill elements into account (if you're curious, take a look at ¨0,"" versus ¨0,"").

    Grade

    See the APL Wiki page for a few more examples. BQN only has the monadic form.

    Grade is more abstract than Sort. Rather than rearranging the argument's cells immediately, it returns a list of indices (more precisely, a permutation) giving the ordering that would sort them.

    @@ -86,7 +86,7 @@

    Bins

    There's also an APL Wiki page on this function, but be careful as the Dyalog version has subtle differences.

    The two Bins functions are written with the same symbols and as Grade, but take two arguments instead of one. More complicated? A little, but once you understand Bins you'll find that it's a basic concept that shows up in the real world all the time.

    -

    Bins behaves like a dyadic search function: it looks up cells from the right argument relative to major cells of the left argument. However, there's an extra requirement: the left argument to Bins is already sorted according to whichever ordering is used. If it isn't, you'll get an error.

    +

    Bins behaves like a search function with respect to rank: it looks up cells from 𝕩 relative to major cells of 𝕨. However, there's an extra requirement: the left argument to Bins is already sorted according to whichever ordering is used. If it isn't, you'll get an error.

    ↗️
        56241  3
     ERROR
         03479  3
    @@ -103,7 +103,7 @@ ERROR
     

    Array ordering

    Most of the time you won't need to worry about the details of how BQN arrays are ordered. It's documented here because, well, that's what documentation does.

    The array ordering defines some arrays to be smaller or larger than others. All of the "Up" ordering functions use this ordering directly, so that smaller arrays come earlier, and the "Down" ones use the opposite ordering, with larger arrays coming earlier. For arrays consisting only of characters and numbers, with arbitrary nesting, the ordering is always defined. If an array contains an operation, trying to order it relative to another array might give an error. If comparing two arrays succeeds, there are three possibilities: the first array is smaller, the second is smaller, or the two arrays match.

    -

    Comparing two atoms is defined to work the same way as the comparison functions ≤<>≥. Numbers come earlier than characters and otherwise these two types are ordered in the obvious way. To compare an atom to an array, the atom enclosing and then compared with the array ordering defined below. The result of this comparison is used except when the two arrays match: in that case, the atom is considered smaller.

    +

    Comparing two atoms is defined to work the same way as the comparison functions ≤<>≥. Numbers come earlier than characters and otherwise these two types are ordered in the obvious way. To compare an atom to an array, the atom enclosing and then compared with the array ordering defined below. The result of this comparison is used except when the two arrays match: in that case, the atom is considered smaller.

    Two arrays of the same shape are compared by comparing all their corresponding elements, in index order. This comparison can stop at the first pair of different elements (which allows later elements to contain operations without causing an error). If any elements were different, then they decide the result of the comparison. If all the elements matched, then by definition the two arrays match.

    The principle for arrays of different shapes is the same, but there are two factors that need to be taken into account. First, it's not obvious any more what it means to compare corresponding elements—what's the correspondence? Second, the two arrays can't match because they have different shapes. So even if all elements end up matching one of them needs to come earlier.

    BQN's array ordering is an extension of the number and character ordering given by to arrays. In this system, any two arrays consisting of only numbers and characters for atoms can be compared with each other. Furthermore, some arrays that contain incomparable atoms (operations) might be comparable, if the result of the comparison can be decided before reaching these atoms. Array ordering does not depend on the fill elements for the two arguments.

    diff --git a/docs/doc/prefixes.html b/docs/doc/prefixes.html index 9ef7cbdd..9732fe90 100644 --- a/docs/doc/prefixes.html +++ b/docs/doc/prefixes.html @@ -11,7 +11,7 @@ "abcde" ⟨ "abcde" "bcde" "cde" "de" "e" ⟨⟩ ⟩
    -

    The functions are closely related to Take and Drop, as we might expect from their glyphs. Element i⊑↑𝕩 is i𝕩, and i⊑↓𝕩 is i𝕩.

    +

    The functions are closely related to Take and Drop, as we might expect from their glyphs. Element i⊑↑𝕩 is i𝕩, and i⊑↓𝕩 is i𝕩.

    In both cases, an empty array and the entire argument are included in the result, meaning its length is one more than that of the argument. Using Span, we can say that the result has elements whose lengths go from 0 to 𝕩, inclusive, so there are (𝕩)¬0 or 1+≠𝕩 elements. The total number or cells in the result (for example, ≠∾↑𝕩 or +´¨𝕩) scales with the square of the argument length—it is quadratic in 𝕩. We can find the exact total by looking at Prefixes and Suffixes together:

    ↗️
        ( ˘ ) "abcde"
     ┌─                 
    @@ -27,7 +27,7 @@
     

    Joining corresponding elements of 𝕩 and 𝕩 gives 𝕩 again. This is because i(↑∾¨)𝕩 is (i⊑↑𝕩)(i⊑↓𝕩), or, using the Take and Drop correspondences, (i𝕩)(i𝕩), which is 𝕩. Element-wise, we are combining the first i cells of 𝕩 with all but the first i. Looking at the entire result, we now know that (↑∾¨)𝕩 is (1+≠𝕩)⥊<𝕩. The total number of cells in this combined array is therefore (1+≠𝕩)×≠𝕩, or 1+×≠𝕩. Each of Prefixes and Suffixes must have the same total number of cells (in fact, 𝕩 is ¨𝕩, and Reverse doesn't change an array's shape). So the total number in either one is 2÷˜1+×≠𝕩. With n𝕩, it is 2÷˜n×1+n.

    Definition

    -

    Knowing the length and the elements, it's easy to define functions for Prefixes and Suffixes: is equivalent to (1+≠)¨< while is (1+≠)¨<. Each primitive is defined only on arrays with at least one axis.

    +

    Knowing the length and the elements, it's easy to define functions for Prefixes and Suffixes: is equivalent to (1+≠)¨< while is (1+≠)¨<. Each primitive is defined only on arrays with at least one axis.

    Working with pairs

    Sometimes it's useful to apply an operation to every unordered pair of elements from a list. For example, consider all possible products of numbers between 1 and 6:

    ↗️
        ×⌜˜ 1+↕6
    @@ -40,7 +40,7 @@
       6 12 18 24 30 36  
                        ┘
     
    -

    It's easy enough to use the Table modifier here, but it also computes most products twice. If we only care about the unique products, we could multiply each number by all the ones after it. "After" sounds like suffixes, so let's look at those:

    +

    It's easy enough to use the Table modifier here, but it also computes most products twice. If we only care about the unique products, we could multiply each number by all the ones after it. "After" sounds like suffixes, so let's look at those:

    ↗️
        1+↕6
     ⟨ 1 2 3 4 5 6 ⟩
          1+↕6
    @@ -52,7 +52,7 @@
         ( × 1  ) 1+↕6
     ⟨ ⟨ 2 3 4 5 6 ⟩ ⟨ 6 8 10 12 ⟩ ⟨ 12 15 18 ⟩ ⟨ 20 24 ⟩ ⟨ 30 ⟩ ⟨⟩ ⟩
     
    -

    By using instead of ×, we can see the argument ordering, demonstrating that we are looking at the upper right half of the matrix produced by Table. While in this case we could use 0 to mimic the pervasion of ×, we'd like this to work even on nested arguments so we should figure out how the mapping structure works to apply Each appropriately.

    +

    By using Couple () instead of ×, we can see the argument ordering, demonstrating that we are looking at the upper right half of the matrix produced by Table. While in this case we could use 0 to mimic the pervasion of ×, we'd like this to work even on nested arguments so we should figure out how the mapping structure works to apply Each appropriately.

    ↗️
        ⌜˜ "abc"
     ┌─                
     ╵ "aa" "ab" "ac"  
    @@ -81,7 +81,7 @@
     · ⟨ ⟨⟩ "a" "ab" "abc" ⟩ ⟨ ⟨⟩ "b" "bc" ⟩ ⟨ ⟨⟩ "c" ⟩ ⟨ ⟨⟩ ⟩  
                                                               ┘
     
    -

    Effectively, this parametrizes the slices either by ending then starting index, or by starting index then length. Four empty slices are included because in a list of length 3 there are 4 places an empty slice can start: all the spaces between or outside elements (these also correspond to all the possible positions for the result of Bins). The slices can also be parametrized by length and then starting index using Windows.

    +

    Effectively, this parametrizes the slices either by ending then starting index, or by starting index then length. Four empty slices are included because in a list of length 3 there are 4 places an empty slice can start: all the spaces between or outside elements (these also correspond to all the possible positions for the result of Bins). The slices can also be parametrized by length and then starting index using Windows.

    ↗️
        ((1+≠)¨<) "abc"
     ┌─                         
     · ┌┐ ┌─    ┌─     ┌─       
    @@ -167,7 +167,7 @@
           ┘     ┘      ┘  
                          ┘
     
    -

    But Prefixes and Suffixes don't have any way to specify that they should work on multiple axes, and always work on exactly one. So to extend this pattern we will have to define multi-dimensional versions. This turns out to be very easy: just replace Length with Shape in the definitions above.

    +

    But Prefixes and Suffixes don't have any way to specify that they should work on multiple axes, and always work on exactly one. So to extend this pattern we will have to define multi-dimensional versions. This turns out to be very easy: just replace Length with Shape in the definitions above.

    ↗️
        Prefs  (1+≢)¨<
         Suffs  (1+≢)¨<
         Prefs¨Suffs 32"abcdef"
    diff --git a/docs/doc/range.html b/docs/doc/range.html
    index c56da6e3..0620d0d8 100644
    --- a/docs/doc/range.html
    +++ b/docs/doc/range.html
    @@ -16,7 +16,7 @@
                               ┘
     

    It's really two different functions packed together: if 𝕩 is a natural number—a length—then it returns a list of numeric indices, but if it's a list of numbers, then it returns an array of list indices. This means the result always has depth one more than the argument.

    -

    The two kinds of index correspond to BQN's two selection functions: Select () works with indices along an axis, which are numbers, and Pick () works with element indices, which are lists. The examples below would fail if we swapped these around. Each result from Range is a length-6 list, but their elements are different.

    +

    The two kinds of index correspond to BQN's two selection functions: Select () works with indices along an axis, which are numbers, and Pick () works with element indices, which are lists. The examples below would fail if we swapped these around. Each result from Range is a length-6 list, but their elements are different.

    ↗️
        6
     ⟨ 0 1 2 3 4 5 ⟩
     
    @@ -29,7 +29,7 @@
         (6)  " pick "
     " pick "
     
    -

    They also correspond to Length () and Shape (): for an array a, ↕≠a gives the indices of major cells, while ↕≢a gives the indices of all elements.

    +

    They also correspond to Length () and Shape (): for an array a, ↕≠a gives the indices of major cells, while ↕≢a gives the indices of all elements.

    ↗️
        a  42@
     
           a
    @@ -85,7 +85,7 @@
         b × ↕≠b
     ⟨ 0 1 2 0 0 0 6 0 ⟩
     
    -

    Now at any given position the index of the last 1, if there is any, is the maximum of all the adjusted indices so far. That's a scan `.

    +

    Now at any given position the index of the last 1, if there is any, is the maximum of all the adjusted indices so far. That's a scan `.

    ↗️
        ` b × ↕≠b
     ⟨ 0 1 2 2 2 2 6 6 ⟩
     
    diff --git a/docs/doc/replicate.html b/docs/doc/replicate.html
    index 2f81bce5..746062ea 100644
    --- a/docs/doc/replicate.html
    +++ b/docs/doc/replicate.html
    @@ -157,7 +157,7 @@
       5 5 5 6 6 6 7 7 7 8 8 8 9 9 9  
                                     ┘
     
    -

    Above, both elements of 𝕨 are enclosed numbers. An individual element doesn't have to be enclosed, but I don't recommend this, since if none of them are enclosed, then 𝕨 will have depth 1 and it will be interpreted as replicating along the first axis only.

    +

    Above, both elements of 𝕨 are enclosed numbers. An individual element doesn't have to be enclosed, but I don't recommend this, since if none of them are enclosed, then 𝕨 will have depth 1 and it will be interpreted as replicating along the first axis only.

    ↗️
        2,3 / b
     ┌─           
     ╵ 0 1 2 3 4  
    @@ -173,7 +173,7 @@
     1
     

    Indices

    -

    The monadic form of / is much simpler than the dyadic one, with no multidimensional case or mismatched argument ranks. 𝕩 must be a list of natural numbers, and /𝕩 is the list 𝕩/↕≠𝕩. Its elements are the indices for 𝕩, with index i repeated i𝕩 times.

    +

    The monadic form of / is much simpler than the dyadic one, with no multidimensional case or mismatched argument ranks. 𝕩 must be a list of natural numbers, and /𝕩 is the list 𝕩/↕≠𝕩. Its elements are the indices for 𝕩, with index i repeated i𝕩 times.

    ↗️
        / 3012
     ⟨ 0 0 0 2 3 3 ⟩
     
    @@ -183,14 +183,14 @@ ⟨ "AB" "CDEFG" ⟨⟩ "H" ⟩

    This function will fail to include trailing empty arrays; the modification (/∾1) fixes this and ensures the result always has as many elements as 𝕨.

    -

    If 𝕩 is boolean then /𝕩 contains all the indices where a 1 appears in 𝕩. Applying -» to the result gives the distance from each 1 to the previous, or to the start of the list, another potentially useful function.

    +

    If 𝕩 is boolean then /𝕩 contains all the indices where a 1 appears in 𝕩. Applying -» to the result gives the distance from each 1 to the previous, or to the start of the list, another potentially useful function.

    ↗️
        / 0101000010
     ⟨ 1 3 8 ⟩
     
         -» / 0101000010
     ⟨ 1 2 5 ⟩
     
    -

    With more effort we can also use / to analyze groups of 1s in the argument (and of course all these methods can be applied to 0s instead, by first flipping the values with ¬). First we highlight the start and end of each group by comparing the list with a shifted copy of itself. Or rather, we'll first place a 0 at the front and then at the end, in order to detect when a group starts at the beginning of the list or ends at the end (there's also a shift-based version, «0𝕩).

    +

    With more effort we can also use / to analyze groups of 1s in the argument (and of course all these methods can be applied to 0s instead, by first flipping the values with ¬). First we highlight the start and end of each group by comparing the list with a shifted copy of itself. Or rather, we'll first place a 0 at the front and then at the end, in order to detect when a group starts at the beginning of the list or ends at the end (there's also a shift-based version, «0𝕩).

    ↗️
        0 (∾≍∾˜) 01110010110
     ┌─                         
     ╵ 0 0 1 1 1 0 0 1 0 1 1 0  
    -- 
    cgit v1.2.3