From 1d6a9cf1441bd6d478977715d82031e77c20ce5c Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Mon, 26 Oct 2020 15:46:38 -0400 Subject: Don't include &run in documentation REPL links: it's no longer used --- docs/doc/based.html | 8 +++---- docs/doc/block.html | 32 +++++++++++++-------------- docs/doc/couple.html | 12 +++++------ docs/doc/depth.html | 30 +++++++++++++------------- docs/doc/functional.html | 8 +++---- docs/doc/group.html | 30 +++++++++++++------------- docs/doc/join.html | 6 +++--- docs/doc/leading.html | 18 ++++++++-------- docs/doc/logic.html | 2 +- docs/doc/prefixes.html | 24 ++++++++++----------- docs/doc/shift.html | 24 ++++++++++----------- docs/doc/syntax.html | 10 ++++----- docs/doc/train.html | 22 +++++++++---------- docs/doc/transpose.html | 22 +++++++++---------- docs/doc/windows.html | 22 +++++++++---------- docs/index.html | 2 +- docs/tutorial/expression.html | 38 ++++++++++++++++---------------- docs/tutorial/list.html | 50 +++++++++++++++++++++---------------------- 18 files changed, 180 insertions(+), 180 deletions(-) (limited to 'docs') diff --git a/docs/doc/based.html b/docs/doc/based.html index c0021093..e070bb94 100644 --- a/docs/doc/based.html +++ b/docs/doc/based.html @@ -12,7 +12,7 @@

APL tends to define its data by starting with the array and then looking downwards in depth at what it contains. The based array model, as the name suggests, starts at the foundations, which in BQN are called "atoms". There are five types of atom, which together with the array type give the six types a value can have in BQN. Based means being yourself, and an atom's not an array.

An atom has depth 0, and doesn't inherently have a shape. However, primitives that expect an array promote atoms by enclosing them to get a rank-0, or unit, array that contains the atom (any value can be enclosed in this way, giving a unit array with higher depth, but it only happens automatically for atoms). Rank and shape both do this, so an atom can be considered to have the same dimensions as a unit array: rank 0 and shape ⟨⟩. An atom is also considered a kind of unit, but it's not a unit array.

Atoms are displayed as plain values, while enclosed atoms, that is, depth-1 unit arrays, are shown with an array display.

-↗️
    3    # Atom
+↗️
    3    # Atom
 3
     <3   # Array
 ┌·   
@@ -22,18 +22,18 @@
 '3'
 

In addition to numbers and characters, functions, 1-modifiers, and 2-modifiers are atoms. We can see this by converting one to a subject role and checking its depth.

-↗️
    Plus  +
+↗️
    Plus  +
      plus
 0
 

The primitives that return a single number, like Rank (=), Length (), and Match (), give it as an atom, not an array.

-↗️
     "abc"
+↗️
     "abc"
 1
       "abc"  # The result was an atom
 0
 

Transposing no axes of an array wouldn't do anything. But because Transpose expects its right argument to be an array, it converts an atom to an array.

-↗️
    ⟨⟩  3
+↗️
    ⟨⟩  3
 ┌·   
 · 3  
     ┘
diff --git a/docs/doc/block.html b/docs/doc/block.html
index 24857cfe..21354448 100644
--- a/docs/doc/block.html
+++ b/docs/doc/block.html
@@ -7,19 +7,19 @@
 

Blocks

In BQN, a block is any piece of code surrounded with curly braces {}. Blocks can be used simply to group statements, or can define functions or modifiers. They are the sole large-scale structure used to organize programs.

Blocks are most commonly used to define functions by including one of the special names for arguments, 𝕨 or 𝕩. With the operands 𝔽 or 𝔾, they can also define 1-modifiers or 2-modifiers.

-↗️
    {𝕩+1} 3
+↗️
    {𝕩+1} 3
 4
     ×{𝕩𝔽𝕩} 4
 16
 

Because they use lexical scoping, blocks can also be used to encapsulate code. If a block uses only variables that it initializes, then it has no dependence on its environment and would work the same way if defined anywhere. But it can also use external variables, defined in a containing block.

-↗️
    ab"outer"
+↗️
    ab"outer"
     { a"inner"  ab }
 ⟨ "inner" "outer" ⟩
 

Headerless blocks

In the simplest case a block is just a list of statements, which are executed to evaluate the block. A block with no special names like 𝕨 or 𝕩 is called an immediate block, and is evaluated as soon as it is reached. The only think such a block does is group some statements, and create a scope for them so that definitions made there are discarded when the block finishes. Even this small amount of functionality could be useful; as an example the following program can build up an array from named components without polluting the rest of the program with those names.

-↗️
    updown  { up5  downup  updown }
+↗️
    updown  { up5  downup  updown }
     updown
 ⟨ 0 1 2 3 4 4 3 2 1 0 ⟩
 
@@ -68,7 +68,7 @@

Of these, 𝕣 is sort of a "more special" character, as we'll discuss below. Except for 𝕣, every special name is a single character and can't have underscores added to spell it as a modifier, allowing a modifier to be applied to a special name with no spacing as in 𝕗_m, something that can't be done with ordinary names.

Arguments

The names 𝕨 and 𝕩, and their uppercase spellings, represent function arguments. As the argument to a function is typically data, it's more common to use the lowercase forms for these. Either of these names will turn an immediate block into a function (or an immediate modifier into a deferred one; see the next section). Instead of being evaluated as soon as it appears in the source, a function is evaluated when it's called, with the special names set to appropriate values. Unlike in Dyalog APL's dfns, their values can be changed like ordinary variables.

-↗️
    {'c'=𝕩} "abcd"
+↗️
    {'c'=𝕩} "abcd"
 ⟨ 0 0 1 0 ⟩
     { 𝕩+2  0𝕩 } 3
 ⟨ 0 5 ⟩
@@ -76,13 +76,13 @@
 ⟨ 5 ¯4 ⟩
 

A function with 𝕨 in its definition doesn't have to be called with two arguments. If it has only one, then 𝕨 is given the special value Nothing ·. This is the only time a variable can ever be Nothing, as an assignment such as v· is not allowed.

-↗️
    3 { (2×𝕨)-𝕩 } 1
+↗️
    3 { (2×𝕨)-𝕩 } 1
 5
       { (2×𝕨)-𝕩 } 1
 ¯1
 

In the second function, 𝕨 behaves just like ·, so that the function 2×𝕨 is not evaluated and - doesn't have a left argument. It has a similar effect when used as the left argument to a function in a train.

-↗️
    "abc" { (𝕨≍⌽) 𝕩 } "def"
+↗️
    "abc" { (𝕨≍⌽) 𝕩 } "def"
 ┌─     
 ╵"abc  
   fed" 
@@ -93,24 +93,24 @@
       ┘
 

However, · can only be used as an argument, and not a list element or operand. Don't use 𝕨 in these ways in a function that could be called monadically. Another potential issue is that and don't work the way you might expect.

-↗️
    { 𝕨 - 𝕩 } 5
+↗️
    { 𝕨 - 𝕩 } 5
 143.413159102577
 

Called dyadically, this function will expand to (𝕨)-𝕩, so we might expect the monadic result to be -𝕩. This sort of expansion isn't right with · on the left. - taken as a whole is a function, so · - 𝕩 is just - 𝕩, or (𝕩)-𝕩, giving the large result seen above.

Operands

The special names 𝔽 and 𝔾, and their lowercase forms, represent operands. Since operands are more often functions, they're typically shown with the uppercase spelling. If 𝔽 is present in a block then it defines a 1-modifier or 2-modifier depending on whether 𝔾 is present; if 𝔾 is there it's always a 2-modifier.

-↗️
    4 {ט𝕗}
+↗️
    4 {ט𝕗}
 16
     2 {𝕗+𝕘} 3
 5
 

As shown above, modifiers without 𝕨, 𝕩, or 𝕤 behave essentially like functions with a higher precedence. These immediate modifiers take operands and return a result of any type. The result is given a function role, so it's most common to return a function, rather than a number as shown above.

-↗️
    _dot_  {𝔽´𝔾}
+↗️
    _dot_  {𝔽´𝔾}
     123 +_dot_× 101
 4
 

However, if one of these names is included, then a deferred modifier is created instead: rather than evaluate the contents when the modifier is called, the operands are bound to it to create a derived function. When this function is called, the statements in the block are evaluated.

-↗️
    +{𝕩𝔽𝕩} 6
+↗️
    +{𝕩𝔽𝕩} 6
 12
     2 {𝔽𝕨,𝔾𝕩}- 5
 ⟨ ⟨ 2 ⟩ ¯5 ⟩
@@ -118,18 +118,18 @@
 

The distinction between an immediate and deferred modifier only matters inside the braces. Once defined, the object is simply a modifier that can be called on operands to return a result. For a deferred modifier this result will always be a function; for an immediate modifier it could be anything.

Self-reference

If a block is assigned a name after it is created, this name can be used for recursion:

-↗️
    Fact  { 𝕩 × (0<)1Fact 𝕩-1 }
+↗️
    Fact  { 𝕩 × (0<)1Fact 𝕩-1 }
     Fact 7
 5040
     (×´1+↕) 7  # There's often a simpler solution than recursion
 5040
 

This is somewhat unsatisfying because it is external to the function being defined, even though it doesn't depend on outside information. Instead, the special name 𝕊 can be used to refer to the function it appears in. This allows anonymous recursive functions to be defined.

-↗️
    { 𝕩 × (0<)1𝕊 𝕩-1 } 7
+↗️
    { 𝕩 × (0<)1𝕊 𝕩-1 } 7
 5040
 

For modifiers, 𝕣 refers to the containing modifier. 𝕊 makes the modifier a deferred modifier like 𝕨 and 𝕩 do, and refers to the derived function. For example, this tail-recursive factorial function uses the operand to accumulate a result, a task that is usually done with a second factorial_helper function in elementary Scheme.

-↗️
    Fact_mod  1 { (0<)1, (𝕨×𝕩)_𝕣 𝕩-1 }
+↗️
    Fact_mod  1 { (0<)1, (𝕨×𝕩)_𝕣 𝕩-1 }
     Fact_mod 7
 1
 
@@ -162,7 +162,7 @@

Unlike these assignments, the header also constrains what inputs the block can take: a monadic 1-modifier like the one above can't take a right operand or left argument, and consequently its body can't contain 𝔾 or 𝕨. Calling it with a left argument, or a right argument that isn't a two-element list, will result in an error.

Destructuring

Arguments, but not operands, allow destructuring like assignment does. While assignment only tolerates lists of variables, header destructuring also allows constants. The argument must match the given structure, including the constants where they appear, or an error results.

-↗️
    Destruct  { 𝕊 a1b,2: ab }
+↗️
    Destruct  { 𝕊 a1b,2: ab }
     Destruct       517,2
 ⟨ 5 7 ⟩
 
@@ -179,14 +179,14 @@

For immediate blocks, this is the only type of header possible, and it must use an identifier as there is no applicable special name. However, this name can't be used, except for returns: it doesn't make sense to refer to a value while it is still being computed!

Multiple bodies

Blocks that define functions and deferred modifiers can include more than one body, separated by semicolons ;. The body used for a particular evaluation is chosen based on the arguments the the block. One special case applies when there are exactly two bodies either without headers or with labels only: in this case, the first applies when there is one argument and the second when there are two.

-↗️
    Ambiv  { 1,𝕩 ; 2,𝕨,𝕩 }
+↗️
    Ambiv  { 1,𝕩 ; 2,𝕨,𝕩 }
     Ambiv 'a'
 ⟨ 1 'a' ⟩
     'a' Ambiv 'b'
 ⟨ 2 'a' 'b' ⟩
 

Bodies before the last two must have headers that include arguments. When a block that includes this type of header is called, its headers are checked in order for compatibility with the arguments. The first body with a compatible header is used.

-↗️
    CaseAdd  { 2𝕊3:05 ; 2𝕊𝕩:1,2+𝕩 ; 𝕊𝕩:2𝕩 }
+↗️
    CaseAdd  { 2𝕊3:05 ; 2𝕊𝕩:1,2+𝕩 ; 𝕊𝕩:2𝕩 }
     2 CaseAdd 3
 ⟨ 0 5 ⟩
     2 CaseAdd 4
diff --git a/docs/doc/couple.html b/docs/doc/couple.html
index 7e14e2d1..fa9b905a 100644
--- a/docs/doc/couple.html
+++ b/docs/doc/couple.html
@@ -6,7 +6,7 @@
 
 

Couple and Merge

Solo/Couple () and Merge (>) are functions that create a higher-rank array from lower-rank components. Each takes some number of inner arrays organized in an outer structure, and creates a single array combining all elements of those inner arrays. For example, let's couple two arrays of shape 23:

-↗️
     p  35×3
+↗️
     p  35×3
 ┌─        
 ╵ 0 3  6  
   0 5 10  
@@ -28,7 +28,7 @@
 ⟨ 2 2 3 ⟩
 

The result has two inner axes that are shared by p and q, preceded by an outer axis: length 2 because there are two arguments. Calling with no left argument does something simpler: because there is one argument, it just adds a length-1 axis to the front. The argument goes solo, becoming the only major cell of the result.

-↗️
     q
+↗️
     q
 ┌─     
 ╎"abc  
   def" 
@@ -37,7 +37,7 @@
 ⟨ 1 2 3 ⟩
 

Merge (>) also takes one argument, but a nested one. Its argument is an array of arrays, each with the same shape. The shape of the result is then the outer shape followed by this shared inner shape.

-↗️
     a  "AB""CD"  "rst""uvw""xyz"
+↗️
     a  "AB""CD"  "rst""uvw""xyz"
 ┌─                         
 ╵ "ABrst" "ABuvw" "ABxyz"  
   "CDrst" "CDuvw" "CDxyz"  
@@ -58,7 +58,7 @@
 

Merge is effectively a generalization of Solo and Couple, since Solo is {>𝕩} and Couple is {>𝕨,𝕩}. Since works on the "list" of arguments, it can only add one dimension, but > can take any number of dimensions as its input.

Merge and array theory

In all cases what these functions do is more like reinterpreting existing data than creating new information. In fact, if we ignore the shape and look at the ravels of the arrays involved in a call to Merge, we find that it just joins them together. Essentially, Merge is a request to ensure that the inner arrays (which, being independent elements, could be any sort of "ragged" array) can fit together in an array, and then to consider them to be such an array. For this reason, Merge (or a virtual analogue) is used to combine the result cells when calling a function with Rank into a single array.

-↗️
     > a
+↗️
     > a
 "ABrstABuvwABxyzCDrstCDuvwCDxyz"
      ¨ a
 ⟨ "ABrst" "ABuvw" "ABxyz" "CDrst" "CDuvw" "CDxyz" ⟩
@@ -66,7 +66,7 @@
 "ABrstABuvwABxyzCDrstCDuvwCDxyz"
 

The way this happens, and the constraint that all inner arrays have the same shape, is closely connected to the concept of an array, and like Table , Merge might be considered a fundamental way to build up multidimensional arrays from lists. In both cases rank-0 or unit arrays are somewhat special. They are the identity element of a function with Table, and can be produced by Merge inverse, > on a list, which forces either the outer or inner shape to be empty (BQN chooses > to be <, but only on an array, as > cannot produce non-arrays). Merge has another catch as well: it cannot produce arrays with a 0 in the shape, except at the end, without some sort of prototype system.

-↗️
     e  ⟨⟩¨ 3
+↗️
     e  ⟨⟩¨ 3
 ⟨ ⟨⟩ ⟨⟩ ⟨⟩ ⟩
      > e
 ⟨ 3 0 ⟩
@@ -77,7 +77,7 @@
 

Coupling units

A note on the topic of Solo and Couple applied to units. As always, one axis will be added, so that the result is a list (strangely, J's laminate differs from Couple in this one case, as it will add an axis to get a shape 21 result). For Solo, this is interchangeable with Deshape (), and either primitive might be chosen for stylistic reasons. For Couple, it is equivalent to Join-to (), but this is an irregular form of Join-to because it is the only case where Join-to adds an axis to both arguments instead of just one. Couple should be preferred in this case.

The pair function, which creates a list from its arguments, can be written Pair <, while in either valence is >Pair. As an interesting consequence, ←→ ><, and the same relationship holds for Pair.

-↗️
    2,3 < "abc"  # Pair two values
+↗️
    2,3 < "abc"  # Pair two values
 ⟨ ⟨ 2 3 ⟩ "abc" ⟩
     < "abc"        # Pair one(?) value
 ⟨ "abc" ⟩
diff --git a/docs/doc/depth.html b/docs/doc/depth.html
index 5e12766b..dcb4f812 100644
--- a/docs/doc/depth.html
+++ b/docs/doc/depth.html
@@ -8,19 +8,19 @@
 

The depth of an array is the greatest level of array nesting it attains, or, put another way, the greatest number of times you can pick an element starting from the original array before reaching an atom. The monadic function Depth () returns the depth of its argument, while the 2-modifier Depth () can control the way its left operand is applied based on the depth of its arguments. Several primitive functions also use the depth of the left argument to decide whether it applies to a single axis of the right argument or to several axes.

The Depth function

To find the depth of an array, use Depth (). For example, the depth of a list of numbers or characters is 1:

-↗️
     234
+↗️
     234
 1
      "a string is a list of characters"
 1
 

Depth is somewhat analogous to an array's rank =𝕩, and in fact rank can be "converted" to depth by splitting rows with <1, reducing the rank by 1 and increasing the depth. Unlike rank, Depth doesn't care at all about its argument's shape:

-↗️
     34"characters"
+↗️
     34"characters"
 1
      (1+↕10)"characters"
 1
 

Also unlike rank, Depth does care about the elements of its argument: in fact, to find the depth of an array, every element must be inspected.

-↗️
     2,3,4,5
+↗️
     2,3,4,5
 1
      2,<3,4,5
 2
@@ -28,7 +28,7 @@
 4
 

As the above expressions suggest, the depth of an array is the maximum of its elements' depths, plus one. The base case, an atom (including a function or modifier), has depth 0.

-↗️
    'c'
+↗️
    'c'
 0
     F+f
 0
@@ -41,7 +41,7 @@
 
DepthIsArray0{1+0´Depth¨𝕩}
 

The minimum element depth of 0 implies that an empty array's depth is 1.

-↗️
    ⟨⟩
+↗️
    ⟨⟩
 1
     2030
 1
@@ -67,34 +67,34 @@
 
 
 

Functions such as Take and Drop use a single number per axis. When the left argument is a list of numbers, they apply to initial axes. But for convenience, a single number is also accepted, and applied to the first axis only. This is equivalent to ravelling the left argument before applying the function.

-↗️
    27777"abc"
+↗️
    27777"abc"
 ⟨ 2 7 7 7 ⟩
     2117777"abc"
 ⟨ 2 1 1 7 ⟩
 

In these cases the flexibility seems trivial because the left argument has depth 1 or 0: it is an array or isn't, and it's obvious what a plain number should do. But for the second row in the table, the left argument is always an array. The general case is that the left argument is a vector and its elements correspond to right argument axes:

-↗️
    32,141  67
+↗️
    32,141  67
 ┌─                         
 ╵ ⟨ 3 1 ⟩ ⟨ 3 4 ⟩ ⟨ 3 1 ⟩  
   ⟨ 2 1 ⟩ ⟨ 2 4 ⟩ ⟨ 2 1 ⟩  
                           ┘
 

This means the left argument is homogeneous of depth 2. What should an argument of depth 1, that is, an array of atoms, do? One option is to continue to require the left argument to be a list, and convert any atom argument into an array by enclosing it:

-↗️
    32,1 <(0=≡)¨ 67
+↗️
    32,1 <(0=≡)¨ 67
 ⟨ ⟨ 3 1 ⟩ ⟨ 2 1 ⟩ ⟩
 

While very consistent, this extension represents a small convenience and makes it difficult to act on a single axis, which for Replicate and Group is probably the most common way the primitive is used:

-↗️
    32123 / "abcde"
+↗️
    32123 / "abcde"
 "aaabbcddeee"
 

With the extension above, every case like this would have to use </ instead of just /. BQN avoids this difficulty by testing the left argument's depth. A depth-1 argument applies to the first axis only, giving the behavior above.

For Select, the depth-1 case is still quite useful, but it may also be desirable to choose a single cell using a list of numbers. In this case the left argument depth can be increased from the bottom using <¨.

-↗️
    214 <¨ 3452
+↗️
    214 <¨ 3452
 ⟨ ⟨ 2 1 4 0 ⟩ ⟨ 2 1 4 1 ⟩ ⟩
 

The Depth modifier

The Depth 2-modifier () is a generalization of Each that allows diving deeper into an array. To illustrate it we'll use a shape 43 array of lists of lists.

-↗️
     n  <12 4322⥊↕48
+↗️
     n  <12 4322⥊↕48
 ┌─                                                                         
 ╵ ⟨ ⟨ 0 1 ⟩ ⟨ 2 3 ⟩ ⟩     ⟨ ⟨ 4 5 ⟩ ⟨ 6 7 ⟩ ⟩     ⟨ ⟨ 8 9 ⟩ ⟨ 10 11 ⟩ ⟩    
   ⟨ ⟨ 12 13 ⟩ ⟨ 14 15 ⟩ ⟩ ⟨ ⟨ 16 17 ⟩ ⟨ 18 19 ⟩ ⟩ ⟨ ⟨ 20 21 ⟩ ⟨ 22 23 ⟩ ⟩  
@@ -105,7 +105,7 @@
 3
 

Reversing n swaps all the rows:

-↗️
     n
+↗️
     n
 ┌─                                                                         
 ╵ ⟨ ⟨ 36 37 ⟩ ⟨ 38 39 ⟩ ⟩ ⟨ ⟨ 40 41 ⟩ ⟨ 42 43 ⟩ ⟩ ⟨ ⟨ 44 45 ⟩ ⟨ 46 47 ⟩ ⟩  
   ⟨ ⟨ 24 25 ⟩ ⟨ 26 27 ⟩ ⟩ ⟨ ⟨ 28 29 ⟩ ⟨ 30 31 ⟩ ⟩ ⟨ ⟨ 32 33 ⟩ ⟨ 34 35 ⟩ ⟩  
@@ -114,7 +114,7 @@
                                                                           ┘
 

Depth ¯1 is equivalent to Each, and reverses the larger vectors, while depth ¯2 applies Each twice to reverse the smaller vectors:

-↗️
    ¯1 n
+↗️
    ¯1 n
 ┌─                                                                         
 ╵ ⟨ ⟨ 2 3 ⟩ ⟨ 0 1 ⟩ ⟩     ⟨ ⟨ 6 7 ⟩ ⟨ 4 5 ⟩ ⟩     ⟨ ⟨ 10 11 ⟩ ⟨ 8 9 ⟩ ⟩    
   ⟨ ⟨ 14 15 ⟩ ⟨ 12 13 ⟩ ⟩ ⟨ ⟨ 18 19 ⟩ ⟨ 16 17 ⟩ ⟩ ⟨ ⟨ 22 23 ⟩ ⟨ 20 21 ⟩ ⟩  
@@ -130,12 +130,12 @@
                                                                           ┘
 

While a negative depth tells how many levels to go down, a non-negative depth gives the maximum depth of the argument before applying the left operand. On a depth-3 array like above, depth 2 is equivalent to ¯1 and depth 1 is equivalent to ¯2. A depth of 0 means to descend all the way to the level of atoms, that is, apply pervasively, like an arithmetic function.

-↗️
    'a',"bc" 0 23,4
+↗️
    'a',"bc" 0 23,4
 ┌─                                                 
 · ⟨ ⟨ 'a' 2 ⟩ ⟨ 'a' 3 ⟩ ⟩ ⟨ ⟨ 'b' 4 ⟩ ⟨ 'c' 4 ⟩ ⟩  
                                                   ┘
 

With a positive operand, Depth doesn't have to use the same depth everywhere. Here, Length is applied as soon as the depth for a particular element is 1 or less, including if the argument has depth 0. For example, it maps over 2,3,4⟩⟩, but not over 11,12, even though these are elements of the same array.

-↗️
    1 1,2,3,4⟩⟩,5,6,7,8,9,10⟩⟩,11,12⟩⟩
+↗️
    1 1,2,3,4⟩⟩,5,6,7,8,9,10⟩⟩,11,12⟩⟩
 ⟨ 1 ⟨ 1 2 ⟩ ⟨ 1 2 3 ⟩ 2 ⟩
 
diff --git a/docs/doc/functional.html b/docs/doc/functional.html index dbbee78a..7debbe20 100644 --- a/docs/doc/functional.html +++ b/docs/doc/functional.html @@ -109,20 +109,20 @@ (-(ט))

Like any function, this one can be given a name and then called. A quirk of this way of defining a function is that it has a subject role (it's the result of the function {𝕎𝕏}´) and so must be defined with a lowercase name.

-↗️
    gauss  {𝕎𝕏}´ -(ט)
+↗️
    gauss  {𝕎𝕏}´ -(ט)
     Gauss 2
 0.0183156388887342
 

Another, and probably more common, use of arrays of functions is to apply several different functions to one or more arguments. Here we apply three different functions to the number 9:

-↗️
    , 2, ⊢-⋆ {𝕎𝕩}¨ 9
+↗️
    , 2, ⊢-⋆ {𝕎𝕩}¨ 9
 ⟨ 3 ⟨ 2 9 ⟩ ¯8094.08392757538 ⟩
 

The 2-modifier Choose () relies on arrays of functions to… function. It's very closely related to Pick , and in fact when the left operand and the elements of the right operand are all data there's no real difference: Choose returns the constant function 𝕗𝕘.

-↗️
    2"abcdef" "arg"
+↗️
    2"abcdef" "arg"
 'c'
 

When the operands contain functions, however, the potential of Choose as a ternary-or-more operator opens up. Here's a function for a step in the Collatz sequence, which halves an even input but multiplies an odd input by 3 and adds 1. To get the sequence for a number, we can apply the same function many times. It's an open problem whether the sequence always ends with the repetition 4, 2, 1, but it can take a surprisingly long time to get there—try 27 as an argument.

-↗️
    (2|)÷2,1+3×⊢¨ 67
+↗️
    (2|)÷2,1+3×⊢¨ 67
 ⟨ 3 22 ⟩
     (2|)÷2,1+3×⊢(10) 6
 ⟨ 6 3 10 5 16 8 4 2 1 4 ⟩
diff --git a/docs/doc/group.html b/docs/doc/group.html
index 0087d70b..2240a89c 100644
--- a/docs/doc/group.html
+++ b/docs/doc/group.html
@@ -8,7 +8,7 @@
 

BQN replaces the Key operator from J or Dyalog APL, and many forms of partitioning, with a single (ambivalent) Group function . This function is somewhat related to the K function = of the same name, but results in an array rather than a dictionary.

Definition

Group operates on a list of atomic-number indices and an array, treated as a list of its major cells or "values", to produce a list of groups, each of which is a selection from those cells. The two arrays have the same length, and each value cell is paired with the index at the same position. That index indicates the result group the cell should go into, with an "index" of ¯1 indicating that it should be dropped and not appear in the result.

-↗️
    01201  "abcde"  # Corresponding indices and values
+↗️
    01201  "abcde"  # Corresponding indices and values
 ┌─                     
 ╵ 0   1   2   0   1    
   'a' 'b' 'c' 'd' 'e'  
@@ -17,7 +17,7 @@
 ⟨ "ad" "be" "c" ⟩
 

For example, we might choose to group a list of words by length. Within each group, cells maintain the ordering they had in the list originally.

-↗️
    phrase  "BQN""uses""notation""as""a""tool""of""thought"
+↗️
    phrase  "BQN""uses""notation""as""a""tool""of""thought"
     ˘ ¨ phrase
 ┌─                   
 ╵ ⟨⟩                 
@@ -33,7 +33,7 @@
 

(Could we define phrase more easily? See below.)

If we'd like to ignore words of 0 letters, or more than 5, we can set all word lengths greater than 5 to 0, then reduce the lengths by 1. Two words end up with left argument values of ¯1 and are omitted from the result.

-↗️
    1 -˜ 5× ¨ phrase
+↗️
    1 -˜ 5× ¨ phrase
 ⟨ 2 3 ¯1 1 0 3 1 ¯1 ⟩
     ˘ {1-˜5×≠¨𝕩} phrase
 ┌─                   
@@ -45,7 +45,7 @@
 

Note that the length of the result is determined by the largest index. So the result never includes trailing empty groups. A reader of the above code might expect 5 groups (lengths 1 through 5), but there are no words of length 5, so the last group isn't there.

When Group is called dyadically, the left argument is used for the indices and the right is used for values, as seen above. When it is called monadically, the right argument, which must be a list, gives the indices and the values grouped are the right argument's indices, that is, ↕≠𝕩.

-↗️
    ˘  23¯12
+↗️
    ˘  23¯12
 ┌─         
 ╵ ⟨⟩       
   ⟨⟩       
@@ -57,7 +57,7 @@
 

Multidimensional grouping

Dyadic Group allows the right argument to be grouped along multiple axes by using a nested left argument. In this case, the left argument must be a list of numeric lists, and the result has rank 𝕨 while its elements—as always—have the same rank as 𝕩. The result shape is 1+⌈´¨𝕨, while the shape of element i𝕨𝕩 is i+´=¨𝕨. If every element of 𝕨 is sorted ascending and contains only non-negative numbers, we have 𝕩≡∾𝕨𝕩, that is, Join is the inverse of Partition.

Here we split up a rank-2 array into a rank-2 array of rank-2 arrays. Along the first axis we simply separate the first pair and second pair of rows—a partition. Along the second axis we separate odd from even indices.

-↗️
    0011,0101010(10×↕4)+7
+↗️
    0011,0101010(10×↕4)+7
 ┌─                              
 ╵ ┌─              ┌─            
   ╵  0  2  4  6   ╵  1  3  5    
@@ -73,17 +73,17 @@
 

The monadic case works similarly: Group Indices always satisfies 𝕩 ←→ 𝕩⊔↕≠1𝕩. As with , the depth of the result of Group Indices is always one greater than that of its argument. A depth-0 argument is not allowed.

Properties

Group is closely related to the inverse of Indices, /. In fact, inverse Indices called on the index argument gives the length of each group:

-↗️
    ¨ 2312
+↗️
    ¨ 2312
 ⟨ 0 1 2 1 ⟩
     / 2312
 ⟨ 0 1 2 1 ⟩
 

A related fact is that calling Indices on the result of Group sorts all the indices passed to Group (removing any ¯1s). This is a kind of counting sort.

-↗️
    /≠¨ 231¯12
+↗️
    /≠¨ 231¯12
 ⟨ 1 2 2 3 ⟩
 

Called dyadically, Group sorts the right argument according to the left and adds some extra structure. If this structure is removed with Join, Group can be thought of as a kind of sorting.

-↗️
     231¯12  "abcde"
+↗️
     231¯12  "abcde"
 "caeb"
     231¯12 {F(0𝕨)/  𝕨F𝕩} "abcde"
 "caeb"
@@ -91,7 +91,7 @@
 

Group can even be implemented with the same techniques as a bucket sort, which can be branchless and fast.

Applications

The obvious application of Group is to group some values according to a known or computed property. If this property isn't an integer, it can be turned into one using Classify (monadic , identical to ). Classify numbers the unique values in its argument by first occurrence.

-↗️
    ln  "Phelps""Latynina""Bjørgen""Andrianov""Bjørndalen"
+↗️
    ln  "Phelps""Latynina""Bjørgen""Andrianov""Bjørndalen"
     co  "US"    "SU"      "NO"     "SU"       "NO"
     ˘ co  ln
 ┌─                            
@@ -101,7 +101,7 @@
                              ┘
 

If we would like a particular index to key correspondence, we can use a fixed left argument to Index Of.

-↗️
    countries  "IT""JP""NO""SU""US"
+↗️
    countries  "IT""JP""NO""SU""US"
     countries ˘ co countries ln
 ┌─                                 
 ╵ "IT" ⟨⟩                          
@@ -112,7 +112,7 @@
                                   ┘
 

However, this solution will fail if there are trailing keys with no values. To force the result to have a particular length you can append that length as a dummy index to each argument, then remove the last group after grouping.

-↗️
    countries  "IT""JP""NO""SU""US""ZW"
+↗️
    countries  "IT""JP""NO""SU""US""ZW"
     countries ˘ co countries{𝕗(¯1↓⊔((𝕗)))} ln
 ┌─                                 
 ╵ "IT" ⟨⟩                          
@@ -125,20 +125,20 @@
 

Partitioning

In examples we have been using a list of strings stranded together. Often it's more convenient to write the string with spaces, and split it up as part of the code. In this case, the index corresponding to each word (that is, each letter in the word) is the number of spaces before it. We can get this number of spaces from a prefix sum on the boolean list which is 1 at each space.

-↗️
    ' '(+`=⊔⊢)"BQN uses notation as a tool of thought"
+↗️
    ' '(+`=⊔⊢)"BQN uses notation as a tool of thought"
 ⟨ "BQN" " uses" " notation" " as" " a" " tool" " of" " thought" ⟩
 

To avoid including spaces in the result, we should change the result index at each space to ¯1. Here is one way to do that:

-↗️
    ' '((⊢-˜¬×+`)=⊔⊢)"BQN uses notation as a tool of thought"
+↗️
    ' '((⊢-˜¬×+`)=⊔⊢)"BQN uses notation as a tool of thought"
 ⟨ "BQN" "uses" "notation" "as" "a" "tool" "of" "thought" ⟩
 

A function with structural Under, such as {¯1¨(𝕩/)+`𝕩}, would also work.

In other cases, we might want to split on spaces, so that words are separated by any number of spaces, and extra spaces don't affect the output. Currently our function makes a new word with each space:

-↗️
    ' '((⊢-˜¬×+`)=⊔⊢)"  string with  spaces   "
+↗️
    ' '((⊢-˜¬×+`)=⊔⊢)"  string with  spaces   "
 ⟨ ⟨⟩ ⟨⟩ "string" "with" ⟨⟩ "spaces" ⟩
 

However, trailing spaces are ignored because Group never produces trailing empty groups (to get them back we would use a dummy final character in the string). To avoid empty words, we should increase the word index only once per group of spaces. We can do this by taking the prefix sum of a list that is 1 only for a space with no space before it. To make such a list, we can use the Shift Before function, giving a list of previous elements. To treat the first element as if it's before a space (so that leading spaces have no effect rather than creating an initial empty group), we shift in a 1.

-↗️
    (⊢≍1»<⊢) ' '="  string with  spaces   "  # All, then filtered, spaces
+↗️
    (⊢≍1»<⊢) ' '="  string with  spaces   "  # All, then filtered, spaces
 ┌─                                                 
 ╵ 1 1 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1  
   0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0  
diff --git a/docs/doc/join.html b/docs/doc/join.html
index 50defc8a..55d58818 100644
--- a/docs/doc/join.html
+++ b/docs/doc/join.html
@@ -7,11 +7,11 @@
 

Join

Join () is an extension of the monadic function Raze from A+ and J to arbitrary argument ranks. It has the same relationship to Join to, the dyadic function sharing the same glyph, as Merge (>) does to Couple (): ab is >ab and ab is ab. While Merge and Couple combine arrays (the elements of Merge's argument, or the arguments themselves for Couple) along a new leading axis, Join and Join to combine them along the existing leading axis. Both Merge and Join can also be called on a higher-rank array, causing Merge to add multiple leading axes while Join combines elements along multiple existing axes.

Join can be used to combine several strings into a single string, like array.join() in Javascript (but it doesn't force the result to be a string).

-↗️
    "time""to""join""some""words"
+↗️
    "time""to""join""some""words"
 "timetojoinsomewords"
 

To join with a separator in between, we might prepend the separator to each string, then remove the leading separator after joining. Another approach would be to insert the separator array as an element between each pair of array elements before calling Join.

-↗️
    1↓∾' '¨"time""to""join""some""words"
+↗️
    1↓∾' '¨"time""to""join""some""words"
 "time to join some words"
 

Join requires each element of its argument to be an array, and their ranks to match exactly. No rank extension is performed.

@@ -21,7 +21,7 @@ RANK ERROR

However, Join has higher-dimensional uses as well. Given a rank-m array of rank-n arrays (requiring mn), it will merge arrays along their first m axes. For example, if the argument is a matrix of matrices representing a block matrix, Join will give the corresponding unblocked matrix as its result.

-↗️
     m  (31425) ¨ 23⥊↕6
+↗️
     m  (31425) ¨ 23⥊↕6
 ┌─                                   
 ╵ ┌─          ┌─      ┌─             
   ╵ 0 0 0 0   ╵ 1 1   ╵ 2 2 2 2 2    
diff --git a/docs/doc/leading.html b/docs/doc/leading.html
index 9e0a182b..9b7113ea 100644
--- a/docs/doc/leading.html
+++ b/docs/doc/leading.html
@@ -9,7 +9,7 @@
 

Monadic functions

Manipulating cells

Most non-arithmetic monadic functions work only on the first axis of the argument—that is, they treat it as a list of its major cells. The function Length () counts these major cells, while Prefixes (), Suffixes (), Reverse (), and First Cell () move them around. The Insert (˝) and Scan (`) modifiers also yield functions that work along the first axis; in contrast, Reduce (´) requires its argument to be a list, as it works on elements.

-↗️
     a  32  "abcdef"  # An array with three major cells
+↗️
     a  32  "abcdef"  # An array with three major cells
 ┌─    
 ╵"ab  
   cd  
@@ -31,7 +31,7 @@
      ┘
 

To use these functions on another axis, use the Rank () or Cells (˘) modifier to find the one you want. For a rank 2 array like a, the most you'll ever need is a single ˘, because a function works on axis 0 by default, and there's only one other axis.

-↗️
    ˘ a                  # First column
+↗️
    ˘ a                  # First column
 "ace"
     ˘ a                  # Swap the columns
 ┌─    
@@ -47,7 +47,7 @@
      ┘
 

In these three cases above, the results are the same as you would get from transposing before and after (this has no effect on the result of ˘, since it has rank 1). But in the following cases, the structure is quite different: a is a list of matrices while ˘a is a matrix of lists. This is because the functions , , and ` leave the trailing axis structure intact ( removes one axis); taking into account that Rank or Cells always preserves the leading or frame axes, all axes are preserved (except the one removed by ). In contrast, Prefixes or Suffixes pushes some axes down in depth, and the number of axes that are pushed down in this way changes with the rank of application. More precisely, these functions move axes after the first from the argument itself to result elements, and create two axes from the first axis, with one of them forming the sole result axis and the other joining the rest as an element axis.

-↗️
     a                   # Prefixes of a:    ranks 1|2
+↗️
     a                   # Prefixes of a:    ranks 1|2
 ┌─                           
 · ↕0‿2 ┌─     ┌─     ┌─      
        ╵"ab"  ╵"ab   ╵"ab    
@@ -71,7 +71,7 @@
      ┘
 

Solo (), something of a maverick, manages to act on zero leading axes of its argument by creating the first axis of the result instead. Because it doesn't need any axis to work, it can go in front of either axis but also past the last one by working with rank 0, a case where most array functions would give an error.

-↗️
      a                 # Solo adds a length-1 axis
+↗️
      a                 # Solo adds a length-1 axis
 ⟨ 1 3 2 ⟩
     a    a             # First Cell undoes this
 1
@@ -82,7 +82,7 @@
 

Comparing cells

The functions in the last section manipulate cells in the same way regardless of what data they contain. Other functions compare cells to each other, either testing whether they match or how they are ordered relative to one another. The two Grade functions ⍋⍒, and the self-comparison functions Unique Mask () and Occurrence Count (), each give a list result, with one number for each cell. We can see below that Occurrence Count returns the same results even as we make the argument cells more complicated, because the changes made preserve the matching of cells.

-↗️
    s  "abracadabra"
+↗️
    s  "abracadabra"
      s
 ⟨ 0 0 0 1 0 2 0 3 1 1 4 ⟩
      ˘ s
@@ -91,7 +91,7 @@
 ⟨ 0 0 0 1 0 2 0 3 1 1 4 ⟩
 

The two Sort functions ∧∨ and Deduplicate () move cells around based on their ordering. The length of Deduplicate's result depends on how many unique cells the argument has, so you'd better be careful if you want to apply it to argument cells! However, the result of sorting has the same shape as the argument, so it can always safely be applied at any rank, for example to the rows of an array.

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

Functions such as Take and Drop use a single number per axis. When the left argument is a list of numbers, they apply to initial axes. Observing the operation of Rotate on the result of Range is instructive:

-↗️
    21  35
+↗️
    21  35
 ┌─                                         
 ╵ ⟨ 2 1 ⟩ ⟨ 2 2 ⟩ ⟨ 2 3 ⟩ ⟨ 2 4 ⟩ ⟨ 2 0 ⟩  
   ⟨ 0 1 ⟩ ⟨ 0 2 ⟩ ⟨ 0 3 ⟩ ⟨ 0 4 ⟩ ⟨ 0 0 ⟩  
@@ -141,13 +141,13 @@
                                           ┘
 

The array is shifted once to the left and twice upward, so that the first index (by ravel order) is now 21⌽↕35 ←→ 21. To see how values are matched to leading axes, we can look at how Drop changes the shape of its argument:

-↗️
     32  7777"abc"
+↗️
     32  7777"abc"
 ⟨ 4 5 7 7 ⟩
 

Functions with single-axis depth 1 tend to be more complicated; see for example Group.

Leading axis agreement

Arithmetic functions, and the Each (¨) and Depth () modifiers, use leading axis agreement to match their arguments together. All axes of the lower-rank argument are matched with the leading axes of the higher-rank one, and axes matched together must have the same length. After pairing axes in this way, a single element of the lower-rank argument might correspond to any number of elements of the higher-rank one. It's reused for each of those corresponding elements.

-↗️
     x  324  60     # A rank-3 array
+↗️
     x  324  60     # A rank-3 array
 ┌─             
 ╎  0  1  2  3  
    4  5  6  7  
diff --git a/docs/doc/logic.html b/docs/doc/logic.html
index 40aa5906..fb679153 100644
--- a/docs/doc/logic.html
+++ b/docs/doc/logic.html
@@ -19,7 +19,7 @@
 

Examples

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

-↗️
    ¬ 00.51
+↗️
    ¬ 00.51
 ⟨ 1 0.5 0 ⟩
 
     ⌜˜ 00.51
diff --git a/docs/doc/prefixes.html b/docs/doc/prefixes.html
index 5f951c07..37bda297 100644
--- a/docs/doc/prefixes.html
+++ b/docs/doc/prefixes.html
@@ -6,14 +6,14 @@
 
 

Prefixes and Suffixes

The Prefixes () function gives a list of all prefixes of its argument array along the first axis, and Suffixes () gives a similar list for suffixes. Because the result can be much larger than the argument, these functions may not be used often in high-performance code, but they are a powerful conceptual tool and can make sense for algorithms that are inherently quadratic.

-↗️
     "abcde"
+↗️
     "abcde"
 ⟨ ⟨⟩ "a" "ab" "abc" "abcd" "abcde" ⟩
      "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𝕩.

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"
+↗️
    ( ˘ ) "abcde"
 ┌─                 
 ╵ ⟨⟩      "abcde"  
   "a"     "bcde"   
@@ -30,7 +30,7 @@
 

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
+↗️
    ×⌜˜ 1+↕6
 ┌─                  
 ╵ 1  2  3  4  5  6  
   2  4  6  8 10 12  
@@ -41,19 +41,19 @@
                    ┘
 

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+↕6
 ⟨ 1 2 3 4 5 6 ⟩
      1+↕6
 ⟨ ⟨ 1 2 3 4 5 6 ⟩ ⟨ 2 3 4 5 6 ⟩ ⟨ 3 4 5 6 ⟩ ⟨ 4 5 6 ⟩ ⟨ 5 6 ⟩ ⟨ 6 ⟩ ⟨⟩ ⟩
 

We want to include the diagonal, so we'll pair each element with the corresponding element of the suffixes, reducing the suffixes to the original array's length by dropping the last element, which is empty. In other cases, we might not want to include it and we should instead drop the first element.

-↗️
    ( ×   ) 1+↕6
+↗️
    ( ×   ) 1+↕6
 ⟨ ⟨ 1 2 3 4 5 6 ⟩ ⟨ 4 6 8 10 12 ⟩ ⟨ 9 12 15 18 ⟩ ⟨ 16 20 24 ⟩ ⟨ 25 30 ⟩ ⟨ 36 ⟩ ⟩
     ( × 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.

-↗️
    ⌜˜ "abc"
+↗️
    ⌜˜ "abc"
 ┌─                
 ╵ "aa" "ab" "ac"  
   "ba" "bb" "bc"  
@@ -63,7 +63,7 @@
 ⟨ ⟨ "aa" "ab" "ac" ⟩ ⟨ "bb" "bc" ⟩ ⟨ "cc" ⟩ ⟩
 

As before, we can exclude the diagonal, and using Prefixes instead of Suffixes gives us the lower left half instead of the upper right—in terms of the arguments given to it reverses the argument pairs and iterates in a different order.

-↗️
    (<˘ ¨¨ 1  ) "abc"
+↗️
    (<˘ ¨¨ 1  ) "abc"
 ⟨ ⟨ "ab" "ac" ⟩ ⟨ "bc" ⟩ ⟨⟩ ⟩
     (<˘ ¨¨ 1  ) "abc"
 ⟨ ⟨ "aa" ⟩ ⟨ "ba" "bb" ⟩ ⟨ "ca" "cb" "cc" ⟩ ⟩
@@ -72,7 +72,7 @@
 

Slices

Prefixes and Suffixes give certain restricted slices of the argument array, where a slice is a contiguous selection of cells. If we want all slices along the first axis, we can take the suffixes of each prefix, or vice-versa:

-↗️
    ¨ "abc"
+↗️
    ¨ "abc"
 ┌─                                                         
 · ⟨ ⟨⟩ ⟩ ⟨ "a" ⟨⟩ ⟩ ⟨ "ab" "b" ⟨⟩ ⟩ ⟨ "abc" "bc" "c" ⟨⟩ ⟩  
                                                           ┘
@@ -82,7 +82,7 @@
                                                           ┘
 

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"
+↗️
    ((1+≠)¨<) "abc"
 ┌─                         
 · ┌┐ ┌─    ┌─     ┌─       
   ╵  ╵"a   ╵"ab   ╵"abc"   
@@ -98,7 +98,7 @@
 

We might view a slice as a selection for not two but three parameters: the number of cells before, in, and after the slice. The conditions are that each parameter, being a length, is at least 0, and the total of the three parameters is equal to the array length. With three parameters and one equality constraint, the space of slices is two-dimensional; the above ways to enumerate it each pick two parameters and allow the third to be dependent on these two. If you're familiar with barycentric coordinates on a triangle, this should sound very familiar because that's exactly what the three parameters are!

We might also consider the question of slices along multiple axes. Because axes are orthogonal, we can choose such a slice by independently slicing along each axis. To use the homogeneous shape of arrays as much as possible, the result should still only have two added layers of nesting for the two coordinates we choose, with all possible choices for the first axis along the axes of the outer array and those for the second along the axes of each inner array. Our Windows-based solution adapts to multidimensional arrays easily:

-↗️
    ((1+≢)<2¨<) 32"abcdef"
+↗️
    ((1+≢)<2¨<) 32"abcdef"
 ┌─                                         
 ╵ ┌─           ┌─              ┌─          
   ╵ ┌┐ ┌┐ ┌┐   ╵ ↕0‿1 ↕0‿1     ╵ ↕0‿2      
@@ -141,7 +141,7 @@
                                           ┘
 

This array can be joined, indicating that the length of each inner axis depends only on the position in the corresponding outer axis (let's also drop those empty slices to take up less space).

-↗️
     11  ((1+≢)<2¨<) 32"abcdef"
+↗️
     11  ((1+≢)<2¨<) 32"abcdef"
 ┌─                    
 ╵ ┌─    ┌─    ┌─      
   ╵"a"  ╵"b"  ╵"ab"   
@@ -168,7 +168,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.

-↗️
    Prefs  (1+≢)¨<
+↗️
    Prefs  (1+≢)¨<
     Suffs  (1+≢)¨<
     Prefs¨Suffs 32"abcdef"
 ┌─                                         
diff --git a/docs/doc/shift.html b/docs/doc/shift.html
index 0ca9f825..b5fc7f08 100644
--- a/docs/doc/shift.html
+++ b/docs/doc/shift.html
@@ -7,13 +7,13 @@
 

Shift functions

The shift functions « and » are two new primitives added to BQN based on a pattern used heavily in the compiler and a reasonable amount everywhere else. Shifts resemble but are more general than the bit-based shift operations used in low-level languages. They replace the APL pattern of a 2-wise reduction after appending or prepending an element (APL's 2≠/0,v translates to »v), one of the more common uses of 2-wise reduction.

The result of a shift function always has the same shape as its right argument. The function adds major cells to the beginning (») or end («) of 𝕩, moving the cells already in 𝕩 to accomodate them. Some cells on the opposite side from those added will "fall off" and not be included in the result.

-↗️
    00 » 321             # Shift Before
+↗️
    00 » 321             # Shift Before
 ⟨ 0 0 3 ⟩
     "end" « "add to the "   # Shift After
 " to the end"
 

The cells to add come from 𝕨 if it's present, as shown above. Otherwise, a single cell of fill values for 𝕩 is used. This kind of shift, which moves cells in 𝕩 over by just one, is called a "nudge".

-↗️
    » "abcd"   # Nudge
+↗️
    » "abcd"   # Nudge
 " abc"
     « 123    # Nudge Back
 ⟨ 2 3 0 ⟩
@@ -21,7 +21,7 @@
 

If 𝕨 is longer than 𝕩, some cells from 𝕨 will be discarded, as well as all of 𝕩. In this case 𝕨»𝕩 is (𝕩)𝕨 and 𝕨«𝕩 is (-≠𝕩)𝕨. For similar reasons, nudging an array of length 0 returns it unchanged.

Sequence processing with shifts

When working with a sequence of data such as text, daily measurements, or audio data, shift functions are generally the best way to handle the concept of "next" or "previous" in the data. In the following example s is shown alongside the shifted-right data »s, and each element is compared to the previous with -», which we see is the inverse of +`.

-↗️
    s  1224356
+↗️
    s  1224356
     s  »s
 ┌─               
 ╵ 1 2 2 4 3 5 6  
@@ -34,14 +34,14 @@
 ⟨ 1 2 2 4 3 5 6 ⟩
 

In this way » refers to a sequence containing the previous element at each position. By default the array's fill is used for the element before the first, and a right argument can be given to provide a different one.

-↗️
     » s
+↗️
     » s
 ⟨ ∞ 1 2 2 4 3 5 ⟩
     » s
 ⟨ 1 1 2 2 4 3 5 ⟩
 

It may appear backwards that », which typically means "go to the next item", is used to represent the previous item. In fact there is no conflict: the symbol » describes what position each cell of 𝕩 will have in the result, but in this context we are interested in knowing what argument value occurs in a particular result position. By moving all numbers into the future we ensure that a number in the present comes from the past. To keep your intuition functioning in these situations, it may help to think of the arrow point as fixed at some position in the result while the tail stretches back to land on the argument position where it comes from.

Switching the direction of the arrow, we get an operation that pulls the next value into each position:

-↗️
    s  «s
+↗️
    s  «s
 ┌─               
 ╵ 1 2 2 4 3 5 6  
   2 2 4 3 5 6 0  
@@ -50,7 +50,7 @@
 ⟨ 1 0 2 ¯1 2 1 ¯6 ⟩
 

The differences here are the same as -» s, except that they are shifted over by one, and it is the last value in the sequence that is compared with a fill value, not the first. These techniques adapt easily to more complicated operations. A symmetric difference is found by subtracting the previous element from the next, and dividing by two:

-↗️
    2÷˜ (»-«) s
+↗️
    2÷˜ (»-«) s
 ⟨ ¯1 ¯0.5 ¯1 ¯0.5 ¯0.5 ¯1.5 2.5 ⟩
 
     2÷˜ (˝» - ˝«) s  # Repeat at the ends instead of using fills
@@ -59,7 +59,7 @@
 

A feature these examples all share is that they maintain the length of s. This is a common condition in sequence processing, particularly when the processed sequence needs to be combined or compared with the original in some way. However, it's not always the case. In some instances, for example when searching s to see if there is any value less than the previous, the list should get shorter during processing. In these cases, Windows () is usually a better choice.

Arithmetic and logical shifts

The glyphs « and », suggesting movement, were chosen for the same reasons as the digraphs << and >>, and can be used to implement the same operations on boolean lists.

-↗️
     i  "10011011"-'0'
+↗️
     i  "10011011"-'0'
 ⟨ 1 0 0 1 1 0 1 1 ⟩
 
     «3 i        # Quick and dirty left shift
@@ -69,7 +69,7 @@
 ⟨ 1 1 0 1 1 0 0 0 ⟩
 

With a number in big-endian format, a right shift might be logical, shifting in zeros, or arithmetic, shifting in copies of the highest-order bit (for little-endian numbers, this applies to left shift rather than right ones). The two kinds of shift can be performed with similar code, using 0 or 𝕩 for the inserted cell.

-↗️
    3 0» i    # Logical right shift
+↗️
    3 0» i    # Logical right shift
 ⟨ 0 0 0 1 0 0 1 1 ⟩
 
     3 (⊏»⊢) i  # Arithmetic right shift
@@ -77,11 +77,11 @@
 

Other examples

In Take (), there's no way to specify the fill element when the result is longer than the argument. To take along the first axis with a specified, constant fill value, you can use Shift Before instead, where the right argument is an array of fills with the desired final shape.

-↗️
    "abc" » 5'F'
+↗️
    "abc" » 5'F'
 "abcFF"
 

When using Scan (`), the result at a particular index is obtained from values up to and including the one at that index. But it's common to want to use the values up to but not including that one instead. This can be done either by joining or shifting in that value before scanning. The difference is that with Join the result is longer than the argument. Either form might be wanted depending on how it will be used.

-↗️
    +` 1111
+↗️
    +` 1111
 ⟨ 1 2 3 4 ⟩
     2 +` 1111
 ⟨ 2 3 4 5 6 ⟩
@@ -89,12 +89,12 @@
 ⟨ 2 3 4 5 ⟩
 

The strides of an array are the distances between one element and the next along any given axis. It's the product of all axis lengths below that axis, since these are all the axes that have to be "skipped" to jump along the axis. The strides of an array 𝕩 are (×`1»⊢)𝕩.

-↗️
    (×`1»⊢) 5243
+↗️
    (×`1»⊢) 5243
 ⟨ 24 12 3 1 ⟩
 

Higher rank

Shifting always works on the first axis of 𝕩 (which must have rank 1 or more), and shifts in major cells. A left argument can have rank equal to 𝕩, or one less than it, in which case it becomes a single cell of the result. With no left argument, a cell of fills 10𝕩 is nudged in.

-↗️
     a  (↕×´) 43
+↗️
     a  (↕×´) 43
 ┌─         
 ╵ 0  1  2  
   3  4  5  
diff --git a/docs/doc/syntax.html b/docs/doc/syntax.html
index df063677..4f8eb345 100644
--- a/docs/doc/syntax.html
+++ b/docs/doc/syntax.html
@@ -107,11 +107,11 @@
 

Constants

BQN has single-token notation for numbers, strings, and characters.

Numbers allow the typical decimal notation with ¯ for the negative sign (because - is a function) and e for scientific notation (or E, as numeric notation is case-insensitive). and π may be used as special numeric values. If complex numbers are supported, then they can be written with the components separated by i. However, no BQN to date supports complex numbers.

-↗️
     ¯π  0.5  5e¯1  1.5E3      # A list of numbers
+↗️
     ¯π  0.5  5e¯1  1.5E3      # A list of numbers
 ⟨ ¯3.14159265358979 0.5 0.5 1500 ∞ ⟩
 

Strings are written with double quotes "", and characters with single quotes '' with a single character in between. A double quote within a string can be escaped by writing it twice; if two string literals are next to each other, they must be separated by a space. In contrast, character literals do not use escapes, as the length is already known.

-↗️
    ¨  "str"  "s't""r"  'c'  '''  '"'    # "" is an escape
+↗️
    ¨  "str"  "s't""r"  'c'  '''  '"'    # "" is an escape
 ⟨ 3 5 1 1 1 ⟩
 
     ¨  "a"  'a'    # A string is an array but a character isn't
@@ -129,7 +129,7 @@
 
 

These roles work exactly like they do in APL, with functions applying to one or two subject arguments, 1-modifiers taking a single function or subject on the left, and 2-modifiers taking a function or subject on each side.

Unlike APL, in BQN the syntactic role of an identifier is determined purely by the way it's spelled: a lowercase first letter (name) makes it a subject, an uppercase first letter (Name) makes it a function, and underscores are used for 1-modifiers (_name) and 2-modifiers (_name_). Below, the function {𝕎𝕩} treats its left argument 𝕎 as a function and its right argument 𝕩 as a subject. With a list of functions, we can make a table of the square and square root of a few numbers:

-↗️
    ט, {𝕎𝕩} 149
+↗️
    ט, {𝕎𝕩} 149
 ┌─         
 ╵ 1 16 81  
   1  2  3  
@@ -138,13 +138,13 @@
 

BQN's built-in operations also have patterns to indicate the syntactic role: 1-modifiers (˜¨˘⁼⌜´`) are all superscript characters, and 2-modifiers (∘○⊸⟜⌾⊘◶⚇⎉⍟) all have an unbroken circle (two functions ⌽⍉ have broken circles with lines through them). Every other built-in constant is a function, although the special symbols ¯, , and π are used as part of numeric literal notation.

Assignment

Another element that can be included in expressions is assignment, which is written with to define (also called "declare" in many other languages) a variable and to change its definition. A variable can only be defined once within a scope, and can only be changed if it has already been defined. However, it can be shadowed, meaning that it is defined again in an inner scope even though it has a definition in an outer scope already.

-↗️
    x1  {x2  x3  x}
+↗️
    x1  {x2  x3  x}
 3
     x
 1
 

Assignment can be used inline in an expression, and its result is always the value being assigned. The role of the identifier used must match the value being assigned.

-↗️
    2×a(Neg-)3
+↗️
    2×a(Neg-)3
 ¯6
     a
 ¯3
diff --git a/docs/doc/train.html b/docs/doc/train.html
index c7f9eeea..d4eff715 100644
--- a/docs/doc/train.html
+++ b/docs/doc/train.html
@@ -9,15 +9,15 @@
 

BQN's trains are the same as those of Dyalog APL, except that Dyalog is missing the minor convenience of BQN's Nothing (·). There are many Dyalog-based documents and videos trains you can view on the APL Wiki.

2-train, 3-train

Trains are an adaptation of the mathematical convention that, for example, two functions F and G can be added to get a new function F+G that applies as (F+G)(x) = F(x)+G(x). With a little change to the syntax, we can do exactly this in BQN:

-↗️
    (⊢+⌽) 5
+↗️
    (⊢+⌽) 5
 ⟨ 4 4 4 4 4 ⟩
 

So given a list of the first few natural numbers, that same list plus its reverse gives a list of just one number repeated many times. I'm sure if I were Gauss I'd be able to find some clever use for that fact. The mathematical convention extends to any central operator and any number of function arguments, which in BQN means we use any three functions, and call the train with a left argument as well—the only numbers of arguments BQN syntax allows are 1 and 2.

-↗️
    7 (+≍-) 2
+↗️
    7 (+≍-) 2
 ⟨ 9 5 ⟩
 

Here Couple () is used to combine two units into a list, so we get seven plus and minus two. It's also possible to leave out the leftmost function of a train, or replace it with ·. In this case the function on the right is called, then the other function is called on its result—it's identical to the mathematical composition , which is also part of BQN.

-↗️
    (∾⌽) "ab""cde""f"
+↗️
    (∾⌽) "ab""cde""f"
 "fcdeab"
     (·∾⌽) "ab""cde""f"
 "fcdeab"
@@ -48,24 +48,24 @@
 

So—although not all trains simplify so much—this confusing train is just {𝕩>¯1»⌈`𝕩}! Why would I write it in such an obtuse way? To someone used to working with trains, the function (⊢>¯1»⌈`) isn't any more complicated to read: in an argument position of a train just means 𝕩 while ` will be applied to the arguments. Using the train just means slightly shorter code and two fewer 𝕩s to trip over.

This function's argument is the self-classify of some list (in fact this technique also works on the self-indices 𝕩𝕩). Self-classify moves along its argument, giving each major cell a number: the first unused natural number if that value hasn't been seen yet, and otherwise the number chosen when it was first seen. It can be implemented as ∊⊐⊢, another train!

-↗️
     sc   "tacittrains"
+↗️
     sc   "tacittrains"
 ⟨ 0 1 2 3 0 0 4 1 3 5 6 ⟩
 

Each 't' is 0, each 'a' is 1, and so on. We'd like to discard some of the information in the self-classify, to just find whether each major cell had a new value. Here are the input and desired result:

-↗️
    sc   "tacittrains"
+↗️
    sc   "tacittrains"
 ┌─                       
 ╵ 0 1 2 3 0 0 4 1 3 5 6  
   1 1 1 1 0 0 1 0 0 1 1  
                         ┘
 

The result should be 1 when a new number appears, higher than all the previous numbers. To do this, we first find the highest previous number by taking the maximum-scan ` of the argument, then shifting to move the previous maximum to the current position. The first cell is always new, so we shift in a ¯1, so it will be less than any element of the argument.

-↗️
    ¯1 » `sc
+↗️
    ¯1 » `sc
 ⟨ ¯1 0 1 2 3 3 3 4 4 4 5 ⟩
     (¯1»⌈`) sc
 ⟨ ¯1 0 1 2 3 3 3 4 4 4 5 ⟩
 

Now we compare the original list with the list of previous-maximums.

-↗️
    sc > ¯1»⌈`sc
+↗️
    sc > ¯1»⌈`sc
 ⟨ 1 1 1 1 0 0 1 0 0 1 1 ⟩
     (⊢>¯1»⌈`) sc
 ⟨ 1 1 1 1 0 0 1 0 0 1 1 ⟩
@@ -73,24 +73,24 @@
 

Composing trains

The example above uses a train with five functions: an odd number. Trains with an odd length are always composed of length-3 trains, and they themselves are composed the same way as subject expressions: an odd-length train can be placed in the last position of another train without parentheses, but it needs parentheses to go in any other position.

But we also saw the length-2 train ∾⌽ above. Even-length trains consist of a single function () applied to a function or odd-length train (); another perspective is that an even-length train is an odd-length train where the left argument of the final (leftmost) function is left out, so it's called with only a right argument. An even-length train always needs parentheses if it's used as one of the functions in another train. However, it can also be turned into an odd-length train by placing · at the left, making the implicit missing argument explicit. After this it can be used at the end of an odd-length train without parentheses. To get some intuition for even-length trains, let's look at an example of three functions used together: the unique () sorted () absolute values (|) of an argument list.

-↗️
    ⍷∧| 34¯3¯20
+↗️
    ⍷∧| 34¯3¯20
 ⟨ 0 2 3 4 ⟩
 

If it doesn't have to be a function, it's easiest to write it all out! Let's assume we want a tacit function instead. With three one-argument functions, we can't use a 3-train, as the middle function in a 3-train always has two arguments. Instead, we will compose the functions with 2-trains. Composition is associative, meaning that this can be done starting at either the left or the right.

-↗️
    ((⍷∧)|) 34¯3¯20
+↗️
    ((⍷∧)|) 34¯3¯20
 ⟨ 0 2 3 4 ⟩
     ((∧|)) 34¯3¯20
 ⟨ 0 2 3 4 ⟩
 

We might make the first train above easier to read by using Atop () instead of a 2-train. Atop is a 2-modifier, so it doesn't need parentheses when used in a train. The second train can also be changed to ⍷∧| in the same way, but there is another option: the rightmost train ∧| can be expanded to ·∧|. After this it's an odd-length train in the last position, and doesn't need parentheses anymore.

-↗️
    (∧|) 34¯3¯20
+↗️
    (∧|) 34¯3¯20
 ⟨ 0 2 3 4 ⟩
     (·∧|) 34¯3¯20
 ⟨ 0 2 3 4 ⟩
 

These two forms have a different emphasis, because the first breaks into subfunctions and | and the second into and ∧|. It's more common to use as a unit than ∧|, so in this case ∧| is probably the better train.

Many one-argument functions strung together is a major weakness for train syntax. If there are many such functions it's probably best to stick with a block function instead!

-↗️
    {⍷∧|𝕩} 34¯3¯20
+↗️
    {⍷∧|𝕩} 34¯3¯20
 ⟨ 0 2 3 4 ⟩
 

In our example, there aren't enough of these functions to really be cumbersome. If is a common combination in a particular program, then the train ∧| will be more visually consistent and make it easier to use a utility function for if that's wanted in the future.

diff --git a/docs/doc/transpose.html b/docs/doc/transpose.html index 0c49210f..ef7b4fcb 100644 --- a/docs/doc/transpose.html +++ b/docs/doc/transpose.html @@ -9,13 +9,13 @@

Monadic Transpose

Transposing a matrix exchanges its axes, mirroring it across the diagonal. APL extends the function to any rank by reversing all axes, but this generalization isn't very natural and is almost never used. The main reason for it is to maintain the equivalence a MP b ←→ a MP b, where MP +˝×1 is the generalized matrix product. But even here APL's Transpose is suspect. It does much more work than it needs to, as we'll see.

BQN's transpose takes the first axis of its argument and moves it to the end.

-↗️
     a23456  23456
+↗️
     a23456  23456
 ⟨ 2 3 4 5 6 ⟩
       a23456
 ⟨ 3 4 5 6 2 ⟩
 

On the argument's ravel, this looks like a simple 2-dimensional transpose: one axis is exchanged with a compound axis made up of the other axes. Here we transpose a rank 3 matrix:

-↗️
    a322  322⥊↕12
+↗️
    a322  322⥊↕12
     < a322
 ┌─                      
 · ┌─        ┌─          
@@ -31,7 +31,7 @@
                        ┘
 

But, reading in ravel order, the argument and result have exactly the same element ordering as for the rank 2 matrix ˘ a322:

-↗️
    < ˘ a322
+↗️
    < ˘ a322
 ┌─                          
 · ┌─            ┌─          
   ╵ 0 1  2  3   ╵ 0 4  8    
@@ -42,18 +42,18 @@
                            ┘
 

To exchange multiple axes, use the Power modifier. Like Rotate, a negative power will move axes in the other direction. In particular, to move the last axis to the front, use Inverse (as you might expect, this exactly inverts ).

-↗️
     3 a23456
+↗️
     3 a23456
 ⟨ 5 6 2 3 4 ⟩
       a23456
 ⟨ 6 2 3 4 5 ⟩
 

In fact, we have ≢⍉k a ←→ k⌽≢a for any number k and array a.

To move axes other than the first, use the Rank modifier in order to leave initial axes untouched. A rank of k>0 transposes only the last k axes while a rank of k<0 ignores the first |k axes.

-↗️
     3 a23456
+↗️
     3 a23456
 ⟨ 2 3 5 6 4 ⟩
 

And of course, Rank and Power can be combined to do more complicated transpositions: move a set of contiguous axes with any starting point and length to the end.

-↗️
     ¯1 a23456
+↗️
     ¯1 a23456
 ⟨ 2 6 3 4 5 ⟩
 

Using these forms, we can state BQN's generalized matrix product swapping rule:

@@ -61,27 +61,27 @@

Certainly not as concise as APL's version, but not a horror either. BQN's rule is actually more parsimonious in that it only performs the axis exchanges necessary for the computation: it moves the two axes that will be paired with the matrix product into place before the product, and directly exchanges all axes afterwards. Each of these steps is equivalent in terms of data movement to a matrix transpose, the simplest nontrivial transpose to perform. Also remember that for two-dimensional matrices both kinds of transposition are the same, and APL's rule holds in BQN.

Axis permutations of the types we've shown generate the complete permutation group on any number of axes, so you could produce any transposition you want with the right sequence of monadic transpositions with Rank. However, this can be unintuitive and tedious. What if you want to transpose the first three axes, leaving the rest alone? With monadic Transpose you have to send some axes to the end, then bring them back to the beginning. For example [following four or five failed tries]:

-↗️
     ¯2  a23456  # Restrict Transpose to the first three axes
+↗️
     ¯2  a23456  # Restrict Transpose to the first three axes
 ⟨ 3 4 2 5 6 ⟩
 

In a case like this BQN's Dyadic transpose is much easier.

Dyadic Transpose

Transpose also allows a left argument that specifies a permutation of the right argument's axes. For each index pi𝕨 in the left argument, axis i of the argument is used for axis p of the result. Multiple argument axes can be sent to the same result axis, in which case that axis goes along a diagonal of the argument array, and the result will have a lower rank than the argument.

-↗️
     13204  a23456
+↗️
     13204  a23456
 ⟨ 5 2 4 3 6 ⟩
      12200  a23456  # Don't worry too much about this case though
 ⟨ 5 2 3 ⟩
 

Since this kind of rearrangement can be counterintuitive, it's often easier to use when specifying all axes. If p≠≢a, then we have pa ←→ p⊏≢a.

-↗️
     13204  a23456
+↗️
     13204  a23456
 ⟨ 3 5 4 2 6 ⟩
 

So far, all like APL. BQN makes one little extension, which is to allow only some axes to be specified. The left argument will be matched up with leading axes of the right argument. Those axes are moved according to the left argument, and remaining axes are placed in order into the gaps between them.

-↗️
     024  a23456
+↗️
     024  a23456
 ⟨ 2 5 3 6 4 ⟩
 

In particular, the case with only one argument specified is interesting. Here, the first axis ends up at the given location. This gives us a much better solution to the problem at the end of the last section.

-↗️
     2  a23456  # Restrict Transpose to the first three axes
+↗️
     2  a23456  # Restrict Transpose to the first three axes
 ⟨ 3 4 2 5 6 ⟩
 

Finally, it's worth noting that, as monadic Transpose moves the first axis to the end, it's equivalent to dyadic Transpose with a "default" left argument: (=-1˙).

diff --git a/docs/doc/windows.html b/docs/doc/windows.html index 1e301c9e..dc84bc73 100644 --- a/docs/doc/windows.html +++ b/docs/doc/windows.html @@ -9,7 +9,7 @@

The Window function replaces APL's Windowed Reduction, J's more general Infix operator, and Dyalog's Stencil, which is adapted from one case of J's Cut operator.

Definition

We'll start with the one-axis case. Here Window's left argument is a number between 0 and 1+≠𝕩. The result is composed of slices of 𝕩 (contiguous sections of major cells) with length 𝕨, starting at each possible index in order.

-↗️
    5"abcdefg"
+↗️
    5"abcdefg"
 ┌─       
 ╵"abcde  
   bcdef  
@@ -18,7 +18,7 @@
 

There are 1+(𝕩)-𝕨, or (𝕩)¬𝕨, of these sections, because the starting index must be at least 0 and at most (𝕩)-𝕨. Another way to find this result is to look at the number of cells in or before a given slice: there are always 𝕨 in the slice and there are only 𝕩 in total, so the number of slices is the range spanned by these two endpoints.

You can take a slice of an array 𝕩 that has length l and starts at index i using li𝕩 or li𝕩. The Prefixes function returns all the slices that end at the end of the array ((𝕩)=i+l), and Suffixes gives the slices that start at the beginning (i=0). Windows gives yet another collection of slices: the ones that have a fixed length l=𝕨. Selecting one cell from its result gives you the slice starting at that cell's index:

-↗️
    25"abcdefg"
+↗️
    25"abcdefg"
 "cdefg"
     52"abcdefg"
 "cdefg"
@@ -26,7 +26,7 @@
 

Windows differs from Prefixes and Suffixes in that it doesn't add a layer of nesting (it doesn't enclose each slice). This is possible because the slices have a fixed size.

Multiple dimensions

The above description applies to a higher-rank right argument. As an example, we'll look at two-row slices of a shape 34 array. For convenience, we will enclose each slice. Note that slices always have the same rank as the argument array.

-↗️
    <2 2"0123""abcd""ABCD"
+↗️
    <2 2"0123""abcd""ABCD"
 ┌─                   
 · ┌─       ┌─        
   ╵"0123   ╵"abcd    
@@ -35,7 +35,7 @@
                     ┘
 

Passing a list as the left argument to Windows takes slices along any number of leading axes. Here are all the shape 22 slices:

-↗️
    <2 22"0123""abcd""ABCD"
+↗️
    <2 22"0123""abcd""ABCD"
 ┌─                      
 ╵ ┌─     ┌─     ┌─      
   ╵"01   ╵"12   ╵"23    
@@ -54,7 +54,7 @@
 

Using Group we could also write iz ←→ 𝕩˜(𝕨()𝕩) +´¨ i.

Symmetry

Let's look at an earlier example, along with its transpose.

-↗️
    {𝕩,𝕩}5"abcdefg"
+↗️
    {𝕩,𝕩}5"abcdefg"
 ┌─                   
 · ┌─        ┌─       
   ╵"abcde   ╵"abc    
@@ -66,29 +66,29 @@
                     ┘
 

Although the two arrays have different shapes, they are identical where they overlap.

-↗️
    (33)5"abcdefg"
+↗️
    (33)5"abcdefg"
 1
 

In other words, the i'th element of slice j is the same as the j'th element of slice i: it is the i+j'th element of the argument. So transposing still gives a possible result of Windows, but with a different slice length.

-↗️
    {(5𝕩)≡⍉(3𝕩)}"abcdefg"
+↗️
    {(5𝕩)≡⍉(3𝕩)}"abcdefg"
 1
 

In general, we need a more complicated transpose—swapping the first set of 𝕨 axes with the second set. Note again the use of Span, our slice-length to slice-number converter.

-↗️
    {((56¬22)𝕩)  23(22𝕩)} 567
+↗️
    {((56¬22)𝕩)  23(22𝕩)} 567
 1
 

Applications

Windows can be followed up with a reduction on each slice to give a windowed reduction. Here we take running sums of 3 values.

-↗️
    +´˘3 2,6,0,1,4,3
+↗️
    +´˘3 2,6,0,1,4,3
 ⟨ 8 7 5 8 ⟩
 

A common task is to act on windows with an initial or final element so the total length stays the same. When using windows of length 2, the best way to accomplish this is with shift functions like « or ». If the window length is longer or variable, then a trick with Windows works better: add the elements, and then use windows matching the original length. Here we invert +`, which requires we take pairwise differences starting at initial value 0.

-↗️
    -(0»⊢) +` 3211
+↗️
    -(0»⊢) +` 3211
 ⟨ 3 2 1 1 ⟩
     (-˜˝≠↕0∾⊢) +` 3211
 ⟨ 3 2 1 1 ⟩
 

With Windows, we can modify the 3-element running sum above to keep the length constant by starting with two zeros.

-↗️
    (+˝≠↕(20)) 2,6,0,1,4,3
+↗️
    (+˝≠↕(20)) 2,6,0,1,4,3
 ⟨ 2 8 8 7 5 8 ⟩
 
diff --git a/docs/index.html b/docs/index.html index ca18bdb4..a07166b1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -45,7 +45,7 @@

It's three letters, that happen to match the capitals in "Big Questions Notation". You can pronounce it "bacon", but are advised to avoid this unless there's puns.

What does BQN look like?

Rather strange, most likely:

-↗️
    ⊑+`122  # The 12th Fibonacci number
+↗️
    ⊑+`122  # The 12th Fibonacci number
 144
 

More snippets are programmed into the live demo at the top of the page: hit the arrow at the right of the code window to see them. For longer samples, you can gaze into the abyss that is the self-hosted compiler, or the shallower but wider abyss of the runtime, or take a look at the friendlier markdown processor used to format and highlight documentation files. This repository also has some translations from "A History of APL in 50 Functions".

diff --git a/docs/tutorial/expression.html b/docs/tutorial/expression.html index bb5fc9d6..cea81292 100644 --- a/docs/tutorial/expression.html +++ b/docs/tutorial/expression.html @@ -8,7 +8,7 @@

Arithmetic

All right, let's get started! Since you can run BQN online from the REPL there aren't any real technical preliminaries, but if you'd like to look at non-web-based options head over to running.md.

In the code blocks shown here, input is highlighted and indented, while output is not colored or indented. To experiment with the code, you can click the ↗️ arrow in the top right corner to open it in the REPL.

-↗️
    2 + 3
+↗️
    2 + 3
 5
     6-   5
 1
@@ -32,7 +32,7 @@
 
 

Shown above are a few arithmetic operations. BQN manages to pass as a normal programming language for three lines so far. That's a big accomplishment for BQN! Earth's a confusing place!

The number of spaces between primitive functions like + and - and their arguments doesn't matter: you can use as much or as little as you like. No spaces inside numbers, of course.

-↗️
    2 × π
+↗️
    2 × π
 6.28318530717959
     9 ÷ 2
 4.5
@@ -66,7 +66,7 @@
 
 

Okay, now BQN looks like normal (grade-school) mathematics, which is sort of like looking normal. Pi (π) represents that real famous number and Infinity () is also part of the number system (the BQN spec allows an implementation to choose its number system, and all existing implementations use double-precision floats, like Javascript or Lua). In analogy with the one-argument form of Minus (-) giving the negation of a number, Divide (÷) with only one argument gives its reciprocal.

A number can be raised to the power of another with Power, written . That's a star rather than an asterisk; BQN doesn't use the asterisk symbol. If it's called without a left argument, then uses a base of Euler's number e and is called Exponential.

-↗️
    2  3
+↗️
    2  3
 8
     3  2
 9
@@ -91,7 +91,7 @@
 
 
 

You could use Power to take square roots and n-th roots, but BQN also provides the primitive for this purpose. If no left argument is provided, then it is the Square Root function; with a left argument it is called Root and raises the right argument to the power of one divided by the left argument.

-↗️
     2
+↗️
     2
 1.4142135623731
     3  27
 3
@@ -104,14 +104,14 @@
 

In case you're wondering, Logarithm—the other inverse of Power—is written . We'll get to it in the section on modifiers.

Compound expressions

It's sometimes useful to write programs with more than one function in them. Here is where BQN and any sort of normality part ways.

-↗️
    2×3 - 5
+↗️
    2×3 - 5
 ¯4
     (2×3) - 5
 1
 

I bet if you try hard you'll remember how much you hated learning to do exponentiation before multiplication and division before addition and subtraction. Didn't I tell you Earth was a confusing place? BQN treats all functions—not just primitives but the ones you'll define as well—the same way. They are evaluated from right to left, and parentheses can be used to group subexpressions that have to be evaluated before being used as arguments.

For a longer example, here's an expression for the volume of a sphere with radius 2.

-↗️
    (4÷3) × π × 23
+↗️
    (4÷3) × π × 23
 33.5103216382911
 

The evaluation order is shown below, with the function on the first line evaluated first, then × on the next, and so on. The effect of the parentheses is that ÷ is evaluated before the leftmost ×.

@@ -158,13 +158,13 @@

The following rule might help you to internalize this system in addition to identifying when parentheses are needed: an expression never needs to end with a parenthesis, or contain two closing parentheses in a row. If it does, at least one set of parentheses can be removed.

One or two arguments?

What about functions without a left argument? Let's find an equation with lots of square roots in it… looks good.

-↗️
     3 + 2 × 2
+↗️
     3 + 2 × 2
 2.4142135623731
     1 + 2
 2.4142135623731
 

They are the same, and now you can't say that BQN is the most complicated thing on this particular page! Just to make sure, we can find the difference by subtracting them, but we need to put the left argument in parentheses:

-↗️
    (3 + 2×√2) - 1+√2
+↗️
    (3 + 2×√2) - 1+√2
 0
 

That's a fairly large expression, so here's another evaluation diagram to check your understanding.

@@ -228,11 +228,11 @@

Perhaps more than you thought! To really get roles, it's important to understand that a role is properly a property of an expression, and not its value. In language implementation terms, roles are used only to parse expressions, giving a syntax tree, but don't dictate what values are possible when the tree is evaluated. So it's possible to have a function with a number role or a number with a function role. The reason this doesn't happen with the numeric literals and primitives we've introduced is that these tokens have a constant value. × or have the same value in any possible program, and so it makes sense that their types and roles should correspond. When we introduce identifiers, we'll see this correspondence break down—and why that's good!

Character arithmetic

Gosh, that's a lot of arithmetic up there. Maybe adding characters will mix things up a bit? Hang on, you can't add characters, only subtract them… let's back up.

-↗️
    'c'
+↗️
    'c'
 'c'
 

A character is written with single quotes. As in the C language, it's not the same as a string, which is written with double quotes. There are no escapes for characters: any source code character between single quotes becomes a character literal, even a newline. Characters permit some kinds of arithmetic:

-↗️
    'c' + 1
+↗️
    'c' + 1
 'd'
     'h' - 'a'
 7
@@ -250,15 +250,15 @@
 
  • Pointers in a computer's memory. I'll let you verify for yourself that the rules are the same.
  • Want to shift an uppercase character to lowercase? Affine characters to the rescue:

    -↗️
        'K' + 'a'-'A'
    +↗️
        'K' + 'a'-'A'
     'k'
     

    Convert a character to a digit? Here you go:

    -↗️
        '4' - '0'
    +↗️
        '4' - '0'
     4
     

    The one thing affine characters won't let you do is find some special "starting character": the main distinguishing factor between a linear and an affine space is that an affine space has no origin. However, for some kinds of programming finding a character's code point is important, so BQN also includes a special literal for the null character, written @:

    -↗️
        '*' - @
    +↗️
        '*' - @
     42
         @ + 97
     'a'
    @@ -280,13 +280,13 @@
     

    Addition and subtraction with affine characters have all the same algebraic properties that they do with numbers. One way to see this is to think of values as a combination of "characterness" (0 for numbers and 1 for characters) and either numeric value or code point. Addition and subtraction are done element-wise on these pairs of numbers, and are allowed if the result is a valid value, that is, its characterness is 0 or 1 and its value is a valid code point if the characterness is 1. However, because the space of values is no longer closed under addition and subtraction, certain rearrangements of valid computations might not be valid, if one of the values produced in the middle isn't legal.

    Modifiers

    Functions are nice and all, but to really bring us into the space age BQN has a second level of function called modifiers (the space age in this case is when operators were introduced to APL in the early 60s—hey, did you know the second APL conference was held at Goddard Space Flight Center?). While functions apply to subjects, modifiers can apply to functions or subjects, and return functions. For example, the 1-modifier ˜ modifies one function by swapping the arguments before calling it (Swap), or copying the right argument to the left if there's only one (Self).

    -↗️
        2 -˜ 'd'  # Subtract from
    +↗️
        2 -˜ 'd'  # Subtract from
     'b'
         +˜ 3      # Add to itself
     6
     

    This gives us two nice ways to square a value:

    -↗️
        ט 4
    +↗️
        ט 4
     16
         2 ˜ 4
     16
    @@ -312,11 +312,11 @@
     
     
     

    Another 1-modifier is Undo (). BQN has just enough computer algebra facilities to look like a tool for Neanderthals next to a real computer algebra system, and among them is the ability to invert some primitives. In general you can't be sure when Undo will work (it might even be undecidable), but the examples I'll give here are guaranteed by the spec to always work in the same way. Starting with a third way to square a number:

    -↗️
         4
    +↗️
         4
     16
     

    The most important use for Undo in arithmetic is the logarithm, written . That's all a logarithm is: it undoes the Power function! With no left argument is the natural logarithm. If there's a left argument then Undo considers it part of the function to be undone. The result in this case is that with two arguments is the logarithm of the right argument with base given by the left one.

    -↗️
         10
    +↗️
         10
     2.30258509299405
         2  32    # Log base 2
     5
    @@ -326,14 +326,14 @@
     4
     

    Another 1-modifier, which at the moment is tremendously useless, is Constant ˙. It turns its operand into a constant function that always returns the operand (inputs to modifiers are called operands because modificands is just too horrible).

    -↗️
        2 3˙ 4
    +↗️
        2 3˙ 4
     3
     

    Well, I guess it's not pedagogically useless, as it does demonstrate that a modifier can be applied to subjects as well as functions. Even though 3 is a subject, 3˙ is a function, and can be applied to and ignore the two arguments 2 and 4.

    With three examples you may have noticed that 1-modifiers tend to cluster at the top of the screen. In fact, every primitive 1-modifer is a superscript character: we've covered ˜⁼˙, and the remaining array-based modifiers ˘¨⌜´˝` will show up later.

    2-modifiers

    Made it to the last role, the 2-modifier (if you think something's been skipped, you're free to call subjects 0-modifiers. They don't modify anything. Just not when other people can hear you). To introduce them we'll use Atop , which works a lot like mathematical composition except that it uses one or two arguments. These arguments are passed to the function on the right, and the result is passed to the function on the left. So the function on the left is only ever called with one argument.

    -↗️
        3 ט+ 4  # Square of 3 plus 4
    +↗️
        3 ט+ 4  # Square of 3 plus 4
     49
         -(ט) 5  # Negative square of 5
     ¯25
    diff --git a/docs/tutorial/list.html b/docs/tutorial/list.html
    index deae0f11..71ad8237 100644
    --- a/docs/tutorial/list.html
    +++ b/docs/tutorial/list.html
    @@ -6,11 +6,11 @@
     
     

    Tutorial: Working with lists

    Enough with all these preliminaries like learning how to read basic expressions. Let's get into what makes BQN special.

    -↗️
        1, 2, 3
    +↗️
        1, 2, 3
     ⟨ 1 2 3 ⟩
     

    This is a list. Wait for it…

    -↗️
        1, 2, 3 + 1
    +↗️
        1, 2, 3 + 1
     ⟨ 2 3 4 ⟩
     

    There we go. Now in BQN arrays are not just lists, which are a 1-dimensional data structure, but can have any number of dimensions. In this tutorial we're going to discuss lists only, leaving the 5-dimensional stuff for later. So we're really only seeing the power of K, an APL-family language that only uses lists (and dictionaries, which BQN doesn't have). K was powerful enough for Arthur Whitney to found two companies and make millions and millions of dollars, and BQN's compiler also runs almost entirely on lists, so this is probably enough power for one webpage.

    @@ -79,7 +79,7 @@

    Strand notation is shorter and looks less cluttered in this example. As with lists, anything goes in a strand, but if it's the result of a function or operator, or another strand, then it has to be put in parentheses. With one set of parentheses, a strand will be just as long as the equivalent bracketed list, and with two you're better off using the list.

    A ligature is a kind of notation and doesn't do something specific like a function does. It's the sequence of ligatures that makes whatever they join together into a list. So if we parenthesize either ligature below, we get a different result! Ligatures aren't right-associative or left-associative.

    -↗️
        012
    +↗️
        012
     ⟨ 0 1 2 ⟩
         (01)2
     ⟨ ⟨ 0 1 ⟩ 2 ⟩
    @@ -113,7 +113,7 @@
     

    Lists are just one-dimensional arrays. Types are divided into data types, which tend to have a subject role, and operation types, which tend to have a role matching their type. Also, any value that's not an array, such as everything we used in the last tutorial, is called an atom.

    Arithmetic on lists

    Arithmetic functions automatically apply to each element of a list argument. If both arguments are lists, they have to have the same length, and they're matched up one element at a time.

    -↗️
        ÷ 2,3,4
    +↗️
        ÷ 2,3,4
     ⟨ 0.5 0.333333333333333 0.25 ⟩
     
         "APL" + 1
    @@ -126,7 +126,7 @@
     ⟨ 4 9 8 1 ⟩
     

    This list application works recursively, so that lists of lists (and so on) are handled as well. We say that arithmetic functions are pervasive. They dig into their arguments until reaching the atoms.

    -↗️
        2 × 02  135
    +↗️
        2 × 02  135
     ⟨ ⟨ 0 4 ⟩ ⟨ 2 6 10 ⟩ ⟩
     
          10, 2030  +  12, 3 
    @@ -156,14 +156,14 @@
     
     

    Let's introduce a few primitives to work with lists.

    Make one or two atom arguments into a list with , pronounced Solo in the one-argument case and Couple in the two-argument case. This might not seem to merit a symbol but there's more to come. Don't call it on lists and ponder the results, igniting a hunger for ever more dimensions.

    -↗️
         4
    +↗️
         4
     ⟨ 4 ⟩
     
         2  4
     ⟨ 2 4 ⟩
     

    Concatenate lists with Join To (). The little chain link symbol—technically "inverted lazy S"—is my favorite in BQN. Hook those lists together!

    -↗️
        1,2,3  "abc"
    +↗️
        1,2,3  "abc"
     ⟨ 1 2 3 'a' 'b' 'c' ⟩
     
         0  1,2,3
    @@ -174,11 +174,11 @@
     

    The last two examples show that you can join a list to an atom, making it the first or last element of the result. This is a little suspect because if you decide the data being stored is more complicated and start using a list instead of an atom, then it will no longer be used as a single element but rather a subsection of the result. So I would only use that shortcut for something like a numeric literal that's clearly an atom and will stay that way, and otherwise wrap those atomic arguments in some ⟨⟩ brackets. Join will even work with two atoms, but in that case I'd say it makes more sense to use Couple instead.

    Reverse () puts the list back to front.

    -↗️
         "drawer"
    +↗️
         "drawer"
     "reward"
     

    With a left argument means Rotate instead, and shifts values over by the specified amount, wrapping those that go off the end to the other side. A positive value rotates to the left, and a negative one rotates right.

    -↗️
        2  0,1,2,3,4
    +↗️
        2  0,1,2,3,4
     ⟨ 2 3 4 0 1 ⟩
         ¯1  "bcdea"
     "abcde"
    @@ -204,7 +204,7 @@
     
     
     

    The 1-modifier Each (¨) applies its operand to every element of a list argument: it's the same as map in a functional programming language. With two list arguments (which have to have the same length), Each pairs the corresponding elements from each, a bit like a zip function. If one argument is a list and one's an atom, the atom is reused every time instead.

    -↗️
        ¨ "abcd""ABCDEF""01"
    +↗️
        ¨ "abcd""ABCDEF""01"
     ⟨ "dcba" "FEDCBA" "10" ⟩
     
         "string""list""array" ¨ 's'
    @@ -214,13 +214,13 @@
     ⟨ "ac" "bb" "ca" ⟩
     

    Fold (´) is the higher-order function also known as reduce or accumulate. It applies its operand function between each pair of elements in a list argument. For example, +´ gives the sum of a list and ×´ gives its product.

    -↗️
        +´ 234
    +↗️
        +´ 234
     9
         ×´ 234
     24
     

    To match the order of BQN evaluation, Fold moves over its argument array from right to left. You'd get the same result by writing the operand function in between each element of the argument list, but you'd also write the function a lot of times.

    -↗️
        -´ 12345
    +↗️
        -´ 12345
     3
         1-2-3-4-5
     3
    @@ -228,11 +228,11 @@
     

    With this evaluation order, -´ gives the alternating sum of its argument. Think of it this way: the left argument of each - is a single number, while the right argument is made up of all the numbers to the right subtracted together. So each - flips the sign of every number to its right, and every number is negated by all the -s to its left. The first number (1 above) never gets negated, the second is negated once, the third is negated twice, returning it to its original value… the signs alternate.

    Hey, isn't it dissonant that the first, second, and third numbers are negated zero, one, and two times? If they were the zeroth, first, and second it wouldn't be…

    You can fold with the Join To function to join several lists together:

    -↗️
        ´  "con", "cat", "enat", "e" 
    +↗️
        ´  "con", "cat", "enat", "e" 
     "concatenate"
     

    But you shouldn't! Just will do the job for you—with no left argument it's just called "Join" (it's like Javascript's .join(), but with no separator and not specific to strings). And it could do more jobs if you had more dimensions. But I'm sure that's the furthest thing from your mind.

    -↗️
          "con", "cat", "enat", "e" 
    +↗️
          "con", "cat", "enat", "e" 
     "concatenate"
     

    Example: base decoding

    @@ -253,19 +253,19 @@

    Almost. It's really close. There are just two things missing, so I'll cover those and can we agree one and three-quarters is pretty good? First is Range (), which is called on a number to give all the natural numbers less than it:

    -↗️
         8
    +↗️
         8
     ⟨ 0 1 2 3 4 5 6 7 ⟩
     

    Natural numbers in BQN start at 0. I'll get to the second function in a moment, but first let's consider how we'd decode just one number in binary. I'll pick a smaller one: 9 is 1001 in binary. Like the first 1 in decimal 1001 counts for one thousand or 103, the first one in binary 1001 counts for 8, which is 23. We can put each number next to its place value like this:

    -↗️
        8421 ¨ 1001
    +↗️
        8421 ¨ 1001
     ⟨ ⟨ 8 1 ⟩ ⟨ 4 0 ⟩ ⟨ 2 0 ⟩ ⟨ 1 1 ⟩ ⟩
     

    To get the value we multiply each number by its place value and then add them up.

    -↗️
        +´ 8421 × 1001
    +↗️
        +´ 8421 × 1001
     9
     

    Now we'd like to generate that list 8421 instead of writing it out, particularly because it needs to be twice as long to decode eight-bit ASCII characters (where the first bit is always zero come on robots would never use such an inefficient format). It's the first four powers of two, or two to the power of the first four natural numbers, in reverse. While we're at it, let's get 1001 from "1001" by subtracting '0'. Nice.

    -↗️
        2  4
    +↗️
        2  4
     ⟨ 1 2 4 8 ⟩
     
         2⋆↕4
    @@ -279,34 +279,34 @@
     

    Lot of functions up there. Notice how I need to use parentheses for the left argument of a function if it's compound, but never for the right argument, and consequently never with a one-argument function.

    Representing our ASCII statement as a list of lists, we convert each digit to a number as before:

    -↗️
        '0' -˜ "01001110""01100101""01110010""01100100""00100001"
    +↗️
        '0' -˜ "01001110""01100101""01110010""01100100""00100001"
     ⟨ ⟨ 0 1 0 0 1 1 1 0 ⟩ ⟨ 0 1 1 0 0 1 0 1 ⟩ ⟨ 0 1 1 1 0 0 1 0 ⟩ ⟨ 0 1 1 0 0 1 0 0 ⟩ ⟨ 0 0 1 0 0 0 0 1 ⟩ ⟩
     

    Now we need to multiply each digit by the right place value, and add them up. The adding part is easy, just requiring an Each.

    -↗️
        +´¨ '0' -˜ "01001110""01100101""01110010""01100100""00100001"
    +↗️
        +´¨ '0' -˜ "01001110""01100101""01110010""01100100""00100001"
     ⟨ 4 4 4 3 2 ⟩
     

    Multiplication is harder, and if we try to multiply by the place value list directly it doesn't go so well.

    -↗️
        (2⋆↕8) × '0' -˜ "01001110""01100101""01110010""01100100""00100001"
    +↗️
        (2⋆↕8) × '0' -˜ "01001110""01100101""01110010""01100100""00100001"
     ERROR

    This is because the list on the left has length 8 while the list on the right has length 5. The elements of the list on the right have length 8, but BQN can't be expected to know you want to connect the two arguments in that particular way. Especially considering that if you happen to have 8 characters then the right argument will have length 8!

    There are a few ways to handle this. What we'll do is bind the place values to × using the 2-modifier . This modifier attaches a left argument to a function.

    -↗️
        "ab" ¨  "cd", "ut" 
    +↗️
        "ab" ¨  "cd", "ut" 
     ⟨ "acd" "but" ⟩
     
         "ab"¨  "cd", "ut" 
     ⟨ "abcd" "abut" ⟩
     

    In the first bit of code above, ¨ matches up its left and right arguments. In the second, we bind "ab" to first—remember that modifiers associate from left to right, so that "ab"¨ and ("ab")¨ are the same—and Each only sees one argument. "ab", packed inside Each's operand, is reused each time. The same principle applies to our binary problem:

    -↗️
        +´¨ (2⋆↕8)ר '0' -˜ "01001110""01100101""01110010""01100100""00100001"
    +↗️
        +´¨ (2⋆↕8)ר '0' -˜ "01001110""01100101""01110010""01100100""00100001"
     ⟨ 78 101 114 100 33 ⟩
     

    To wrap things up, we just convert from numbers to characters by adding the null character.

    -↗️
        @ + +´¨ (2⋆↕8)ר '0' -˜ "01001110""01100101""01110010""01100100""00100001"
    +↗️
        @ + +´¨ (2⋆↕8)ר '0' -˜ "01001110""01100101""01110010""01100100""00100001"
     "Nerd!"
     

    Was it as anticlimactic as you'd hoped? In fact there's a simpler way to do the base decoding as well, using 's mirror image in a different way. We'll discuss that in the next tutorial!

    -↗️
        +´ (2⋆↕4) × "1001"-'0'
    +↗️
        +´ (2⋆↕4) × "1001"-'0'
     9
         +(+˜)´  "1001"-'0'
     9
    -- 
    cgit v1.2.3