diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/context.md | 58 | ||||
| -rw-r--r-- | doc/depth.md | 12 | ||||
| -rw-r--r-- | doc/fromDyalog.md | 2 | ||||
| -rw-r--r-- | doc/functional.md | 18 | ||||
| -rw-r--r-- | doc/group.md | 12 | ||||
| -rw-r--r-- | doc/indices.md | 22 | ||||
| -rw-r--r-- | doc/join.md | 6 | ||||
| -rw-r--r-- | doc/logic.md | 2 | ||||
| -rw-r--r-- | doc/transpose.md | 6 | ||||
| -rw-r--r-- | doc/windows.md | 6 |
10 files changed, 72 insertions, 72 deletions
diff --git a/doc/context.md b/doc/context.md index fcd51bde..e3328fc8 100644 --- a/doc/context.md +++ b/doc/context.md @@ -13,17 +13,17 @@ In each case, some values are used as inputs to functions while others are the f a B C _d e -Here, the lowercase spelling indicates that `a` and `e` are to be treated as values ("arrays" in APL) while the uppercase spelling of variables `B` and `C` are used as functions and `_d` is a modifier ("monadic operator"). Like parentheses for function application, the spelling is not inherent to the variable values used, but instead indicates their grammatical role in this particular expression. A variable has no inherent spelling and can be used in any role, so the names `a`, `A`, `_a`, and `_a_` all refer to exact same variable, but in different roles; typically we use the lowercase name to refer to the variable in isolation. While we still don't know anything about what values `a`, `b`, `c`, and so on have, we know how they interact in the line of code above. +Here, the lowercase spelling indicates that `a` and `e` are to be treated as subjects ("arrays" in APL) while the uppercase spelling of variables `B` and `C` are used as functions and `_d` is a 1-modifier ("monadic operator"). Like parentheses for function application, the spelling is not inherent to the variable values used, but instead indicates their grammatical role in this particular expression. A variable has no inherent spelling and can be used in any role, so the names `a`, `A`, `_a`, and `_a_` all refer to exact same variable, but in different roles; typically we use the lowercase name to refer to the variable in isolation—all values are nouns when speaking about them in English. While we still don't know anything about what values `a`, `b`, `c`, and so on have, we know how they interact in the line of code above. ## Is grammatical context really a problem? Yes, in the sense of [problems with BQN](../problems.md). A grammar that uses context is harder for humans to read and machines to execute. A particular difficulty is that parts of an expression you don't yet understand can interfere with parts you do, making it difficult to work through an unknown codebase. -One difficulty beginners to APL will encounter is that code in APL at first appears like a string of undifferentiated symbols. For example, a tacit Unique Mask implementation `⍳⍨=⍳∘≢` consists of six largely unfamiliar characters with little to distinguish them (in fact, the one obvious bit of structure, the repeated `⍳`, is misleading as it means different things in each case!). Simply placing parentheses into the expression, like `(⍳⍨)=(⍳∘≢)`, can be a great help to a beginner, and part of learning APL is to naturally see where the parentheses should go. The equivalent BQN expression, `⊐˜=↕∘≠`, will likely appear equally intimidating at first, but the path to learning which things apply to which is much shorter: rather than learning the entire list of APL primitives, a beginner just needs to know that superscript characters like `˜` are modifiers and characters like `∘` with unbroken circles are compositions before beginning to learn the BQN grammar that will explain how to tie the various parts together. +One difficulty beginners to APL will encounter is that code in APL at first appears like a string of undifferentiated symbols. For example, a tacit Unique Mask implementation `⍳⍨=⍳∘≢` consists of six largely unfamiliar characters with little to distinguish them (in fact, the one obvious bit of structure, the repeated `⍳`, is misleading as it means different things in each case!). Simply placing parentheses into the expression, like `(⍳⍨)=(⍳∘≢)`, can be a great help to a beginner, and part of learning APL is to naturally see where the parentheses should go. The equivalent BQN expression, `⊐˜=↕∘≠`, will likely appear equally intimidating at first, but the path to learning which things apply to which is much shorter: rather than learning the entire list of APL primitives, a beginner just needs to know that superscript characters like `˜` are 1-modifiers and characters like `∘` with unbroken circles are 2-modifiers before beginning to learn the BQN grammar that will explain how to tie the various parts together. This sounds like a distant concern to a master of APL or a computer that has no difficulty memorizing a few dozen glyphs. Quite the opposite: the same concern applies to variables whenever you begin work with an unfamiliar codebase! Many APL programmers even enforce variable name conventions to ensure they know the class of a variable. By having such a system built in, BQN keeps you from having to rely on programmers following a style guide, and also allows greater flexibility, including [functional programming](functional.md), as we'll see later. -Shouldn't a codebase define all the variables it uses, so we can see their class from the definition? Not always: consider that in a language with libraries, code might be imported from dependencies. Many APLs also have some dynamic features that can allow a variable to have more than one class, such as the `⍺←⊢` pattern in a dfn that makes `⍺` an array in the dyadic case but a function in the monadic case. Regardless, searching for a definition somewhere in the code is certainly a lot more work than knowing the class right away! One final difficulty is that even one unknown can delay understanding of an entire expression. Suppose in `A B c`, `B` is a function and `c` is an array, and both values are known to be constant. If `A` is known to be a function (even if its value is not yet known), its right argument `B c` can be evaluated ahead of time. But if `A`'s type isn't known, it's impossible to know if this optimization is worth it, because if it is an array, `B` will instead be called dyadically. +Shouldn't a codebase define all the variables it uses, so we can see their class from the definition? Not always: consider that in a language with libraries, code might be imported from dependencies. Many APLs also have some dynamic features that can allow a variable to have more than one class, such as the `⍺←⊢` pattern in a dfn that makes `⍺` an array in the dyadic case but a function in the monadic case. Regardless, searching for a definition somewhere in the code is certainly a lot more work than knowing the class just from looking! One final difficulty is that even one unknown can delay understanding of an entire expression. Suppose in `A B c`, `B` is a function and `c` is an array, and both values are known to be constant. If `A` is known to be a function (even if its value is not yet known), its right argument `B c` can be evaluated ahead of time. But if `A`'s type isn't known, it's impossible to know if this optimization is worth it, because if it is an array, `B` will instead be called dyadically. ## BQN's spelling system @@ -31,52 +31,52 @@ BQN's expression grammar is a simplified version of the typical APL, removing so | BQN | APL | J |-------------|------------------|------ -| Value | Array | Noun +| Subject | Array | Noun | Function | Function | Verb -| Modifier | Monadic operator | Adverb -| Composition | Dyadic operator | Conjunction +| 1-modifier | Monadic operator | Adverb +| 2-modifier | Dyadic operator | Conjunction -Unlike variables, BQN primitives have only one spelling, and a fixed role (but their values can be used in a different role by storing them in variables). Superscript glyphs `` ˜¨˘⁼⌜´` `` are used for modifiers, and glyphs `∘○⊸⟜⌾⊘◶⚇⎉⍟` with an unbroken circle are compositions. Other primitives are functions. String and numeric literals are values. +Unlike variables, BQN primitives have only one spelling, and a fixed role (but their values can be used in a different role by storing them in variables). Superscript glyphs `` ˜¨˘⁼⌜´` `` are used for 1-modifiers, and glyphs `∘○⊸⟜⌾⊘◶⚇⎉⍟` with an unbroken circle are 2-modifiers. Other primitives are functions. String and numeric literals are subjects. -BQN's variables use another system, where the spelling indicates how the variable's value is used. A variable spelled with a lowercase first letter, like `var`, is a value. Spelled with an uppercase first letter, like `Var`, it is a function. Underscores are placed where operands apply to indicate a modifier `_var` or composition `_var_`. Other than the first letter or underscore, variables are case-insensitive. +BQN's variables use another system, where the spelling indicates how the variable's value is used. A variable spelled with a lowercase first letter, like `var`, is a subject. Spelled with an uppercase first letter, like `Var`, it is a function. Underscores are placed where operands apply to indicate a 1-modifier `_var` or 2-modifier `_var_`. Other than the first letter or underscore, variables are case-insensitive. The associations between spelling and syntactic role are considered part of BQN's [token formation rules](../spec/token.md). -One rule for typing is also best considered to be a pre-parsing rule like the spelling system: the role of a brace construct `{}` with no header is determined by which special arguments it uses: it's a value if there are none, but a `𝕨` or `𝕩` makes it at least a function, an `𝔽` makes it a modifier or composition, and a `𝔾` always makes it a composition. +One rule for typing is also best considered to be a pre-parsing rule like the spelling system: the role of a brace construct `{}` with no header is determined by which special arguments it uses: it's a subject if there are none, but a `𝕨` or `𝕩` makes it at least a function, an `𝔽` makes it a 1- or 2-modifier, and a `𝔾` always makes it a 2-modifier. ## BQN's grammar A formal treatment is included in [the spec](../spec/grammar.md). BQN's grammar—the ways syntactic roles interact—follows the original APL model (plus trains) closely, with allowances for new features like list notation. In order to keep BQN's syntax context-free, the syntactic role of any expression must be known from its contents, just like tokens. -Here is a table of the APL-derived operator and function application rules: +Here is a table of the APL-derived modifier and function application rules: -| left | main | right | output | name -|-------|-------|-------|----------|------ -| | `F` | `x` | Value | Monadic function -| `w` | `F` | `x` | Value | Dyadic function -| | `F` | `G` | Function | 2-train -| `F*` | `G` | `H` | Function | 3-train -| `F*` | `_m` | | Function | Modifier -| `F*` | `_c_` | `G*` | Function | Composition -| | `_c_` | `G*` | Modifier | Partial application -| `F*` | `_c_` | | Modifier | Partial application +| left | main | right | output | name +|-------|-------|-------|------------|------ +| | `F` | `x` | Subject | Monadic function +| `w` | `F` | `x` | Subject | Dyadic function +| | `F` | `G` | Function | 2-train +| `F*` | `G` | `H` | Function | 3-train +| `F*` | `_m` | | Function | 1-Modifier +| `F*` | `_c_` | `G*` | Function | 2-Modifier +| | `_c_` | `G*` | 1-Modifier | Partial application +| `F*` | `_c_` | | 1-Modifier | Partial application -A function with an asterisk indicates that a value can also be used: in these positions there is no difference between function and value spellings. Operator applications bind more tightly than functions, and associate left-to-right while functions associate right-to-left. +A function with an asterisk indicates that a subject can also be used: in these positions there is no difference between function and subject spellings. Modifier applications bind more tightly than functions, and associate left-to-right while functions associate right-to-left. -BQN lists can be written with angle brackets `⟨elt0,elt1,…⟩` or ligatures `elt0‿elt1‿…`. In either case the elements can have any type, and the result is a value. +BQN lists can be written with angle brackets `⟨elt0,elt1,…⟩` or ligatures `elt0‿elt1‿…`. In either case the elements can have any type, and the result is a subject. -The statements in a brace block, function, or operator can also be any role, including the return value at the end. These roles have no effect: outside of braces, a function always returns an array, a modifier always returns a function, and so on, regardless of how these objects were defined. +The statements in a block can also be any role, including the return value at the end. These roles have no effect: outside of braces, an immediate block is a subject, a function always returns a subject, and a modifier always returns a function, regardless of how these objects were defined. ## Mixing roles -BQN's basic types align closely with its syntactic roles: functions, modifiers, and compositions are all basic types, while values are split into numbers, characters, and arrays. This is no accident, and usually values will be used in roles that match their underlying type. However, the ability to use a role that doesn't match the type is very useful. +BQN's value types align closely with its syntactic roles: functions, 1-modifiers, and 2-modifiers are all types (*operation* types) as well as roles, while the other types (*data* types) are split into numbers, characters, and arrays. This is no accident, and usually values will be used in roles that correspond to their underlying type. However, the ability to use a role that doesn't match the type is also useful. -Any type can be passed as an argument to a function, or as an operand, by treating it as a value. This means that BQN fully supports Lisp-style [functional programming](functional.md), where functions can be used as values. +Any type can be passed as an argument to a function, or as an operand, by treating it as a subject. This means that BQN fully supports Lisp-style [functional programming](functional.md), where functions can be used as first-class entities. -It can also be useful to treat a value type as a function, in which case it applies as a constant function. This rule is useful with most built-in operators. For example, `F⎉1` uses a constant for the rank even though in general a function can be given, and `a⌾(b⊸/)` inserts the values in `a` into the positions selected by `b`, ignoring the old values rather than applying a function to them. +It can also be useful to treat a value of a data type as a function, in which case it applies as a constant function. This rule is useful with most built-in modifiers. For example, `F⎉1` uses a constant for the rank even though in general a function can be given, and if `a` is an array then `a⌾(b⊸/)` inserts the values in `a` into the positions selected by `b`, ignoring the old values rather than applying a function to them. -Other mixes of roles are generally not useful. While a combination such as treating a function as a modifier is allowed, attempting to apply it to an operand will fail. Only a modifier can be applied as a modifier and only a composition can be applied as a composition. Only a function or value can be applied as a function. +Other mixes of roles are generally not useful. While a combination such as treating a function as a modifier is allowed, attempting to apply it to an operand will fail. Only a 1-modifier can be applied as a 1-modifier and only a 2-modifier can be applied as a 2-modifier. Only a function or data can be applied as a function. -It's also worth noting that something that appears to be a value may actually be a function! For example, the result of `𝕨˜𝕩` may not always be `𝕨`. `𝕨˜𝕩` is exactly identical to `𝕎˜𝕩`, which gives `𝕩𝕎𝕩`. If `𝕎` is a number, character, or array, that's the same as `𝕨`, but if it is a function, then it will be applied. +It's also worth noting that a subject may unexpectedly be a function! For example, the result of `𝕨˜𝕩` may not always be `𝕨`. `𝕨˜𝕩` is exactly identical to `𝕎˜𝕩`, which gives `𝕩𝕎𝕩`. If `𝕎` is a number, character, or array, that's the same as `𝕨`, but if it is a function, then it will be applied. -The primary way to change the role of a value in BQN is to use a name, including one of the arguments to a brace function. In particular, you can use `{𝔽}` to convert a value operand into a function. Converting a function to a value is more difficult. Often an array of functions is wanted, in which case they can be stranded together; otherwise it's probably best to give the function a name. Picking a function out of a list, for example `⊑⟨+⟩` will give it as a value. +The primary way to change the role of a value in BQN is to use a name, including one of the special names for inputs to a brace function or modifier. In particular, you can use `{𝔽}` to convert a subject operand into a function. Converting a function to a subject is more difficult. Often an array of functions is wanted, in which case they can be stranded together; otherwise it's probably best to give the function a name. Picking a function out of a list, for example `⊑⟨+⟩`, will give it as a subject. diff --git a/doc/depth.md b/doc/depth.md index 2bb369ef..a901982c 100644 --- a/doc/depth.md +++ b/doc/depth.md @@ -1,6 +1,6 @@ # Depth -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 a non-array. The monadic function Depth (`≡`) returns the depth of its argument, while the composition 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 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 a non-array. 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 @@ -27,7 +27,7 @@ Also unlike rank, Depth *does* care about the elements of its argument: in fact, ≡ ⟨2,<3,4,<<<5⟩ 4 -As the above expressions suggest, the depth of an array is the maximum of its elements, plus one. The base case, a non-array (including a function, modifier, or combinator), has depth 0. +As the above expressions suggest, the depth of an array is the maximum of its elements' depths, plus one. The base case, a non-array (including a function or modifier), has depth 0. ≡'c' 0 @@ -38,7 +38,7 @@ As the above expressions suggest, the depth of an array is the maximum of its el ≡⟨5,⟨'c',f,2⟩⟩ 2 -If the function `IsArray` indicates whether its argument is an array, then we can write a recursive definition of Depth using the Choose composition. +If the function `IsArray` indicates whether its argument is an array, then we can write a recursive definition of Depth using the Choose modifier. Depth←IsArray◶0‿{1+0⌈´Depth¨⥊𝕩} @@ -73,7 +73,7 @@ In these cases the flexibility seems trivial because the left argument has depth [ 2 1 ] [ 2 4 ] [ 2 1 ] ┘ -This means the left argument is homogeneous of depth 2. What should an argument of depth 1, or an argument that contains non-arrays, do? One option is to continue to require the left argument to be a vector, and convert any non-array argument into an array by boxing it: +This means the left argument is homogeneous of depth 2. What should an argument of depth 1, or an argument that contains non-arrays, do? One option is to continue to require the left argument to be a list, and convert any non-array argument into an array by enclosing it: ⟨3‿2,1⟩ <⍟(0=≡)¨⊸⊏ ↕6‿7 [ [ 3 1 ] [ 2 1 ] ] @@ -90,9 +90,9 @@ For Select, the depth-1 case is still quite useful, but it may also be desirable 2‿1‿4 <¨⊸⊏ ↕3‿4‿5‿2 [ [ 2 1 4 0 ] [ 2 1 4 1 ] ] -## The Depth composition +## The Depth modifier -The Depth composition (`⚇`) is a generalization of Each that allows diving deeper into an array. To illustrate it we'll use a shape `4‿3` array of lists of lists. +The Depth 2-modifier (`⚇`) is a generalization of Each that allows diving deeper into an array. To illustrate it we'll use a shape `4‿3` array of lists of lists. ⊢ n ← <⎉1⍟2 4‿3‿2‿2⥊↕48 ┌ diff --git a/doc/fromDyalog.md b/doc/fromDyalog.md index bbb99376..ced9c774 100644 --- a/doc/fromDyalog.md +++ b/doc/fromDyalog.md @@ -16,7 +16,7 @@ Here are some closest equivalents in Dyalog APL for the BQN functions that don't | Monad | `,⍀` | `⌽,⌽⍀⌽` | `⍳` | `⍸` | `⍋` | `⍒` | `⊣⌿` | `⊃` | | `…` | `≠` | `∪` | `⌸` | | Dyad | `↑` | `↓` | `,⌿` | `⌿` | `⍸` | `⌽⍸⌽` | `⌷` | `⊃` | `⍳` | `…` | `∊` | `⍷` | `⌸` or `⊆` | -Modifiers and combinators are a little harder. Many have equivalents in some cases, but Dyalog sometimes chooses different functionality based on whether the operand is an array. In BQN an array is always treated as a constant function. +Modifiers are a little harder. Many have equivalents in some cases, but Dyalog sometimes chooses different functionality based on whether the operand is an array. In BQN an array is always treated as a constant function. | BQN | `¨` | `⌜` | `´` | `⎉` | `⍟` | `˜` | `∘` | `○` | `⟜` | |--------|-----|------|-----|-----|-----|-----|-----|-----|-----| diff --git a/doc/functional.md b/doc/functional.md index edc20c5f..45614904 100644 --- a/doc/functional.md +++ b/doc/functional.md @@ -4,7 +4,7 @@ BQN boasts of its functional capabilities, including first-class functions. What First, let's be clear about what the terms we're using mean. A language has *first-class functions* when functions (however they are defined) can be used in all the same ways as "ordinary" values like numbers and so on, such as being passed as an argument or placed in a list. Lisp and JavaScript have first-class functions, C has unsafe first-class functions via function pointers, and Java and APL don't have them as functions can't be placed in lists or used as arguments. This doesn't mean every operation is supported on functions: for instance, numbers can be added, compared, and sorted; while functions could perhaps be added to give a train, comparing or sorting them as functions (not representations) isn't computable, and BQN doesn't support any of the three operations when passing functions as arguments. -Traditionally APL has worked around its lack of first-class functions with operators or second-order functions. Arrays in APL are first class while functions are second class and operators are third class, and each class can act on the ones before it. However, the three-tier system has some obvious limitations that we'll discuss, and BQN removes these by making every type first class. +Traditionally, APL has worked around its lack of first-class functions with operators, that is, second-order functions. Arrays in APL are first class while functions are second class and operators are third class, and each class can act on the ones before it. However, the three-tier system has some obvious limitations that we'll discuss, and BQN removes these by making every type first class. The term *functional programming* is more contentious, and has many meanings some of which can be vague. Here I use it for what might be called *first-class functional programming*, programming that makes significant use of first-class functions; in this usage, Scheme is probably the archetypal functional programming language. However, two other definitions are also worth mentioning. APL is often called a functional programming language on the grounds that functions can be assigned and manipulated, and called recursively, all characteristics it shares with Lisp. I prefer the term *function-level programming* for this usage. A newer usage, which I call *pure functional programming*, restricts the term "function" to mathematical functions, which have no side effects, so that functional programming is programming with no side effects, often using monads to accumulate effects as part of arguments and results instead. Finally, *typed functional programming* is closely associated with pure functional programming and refers to statically-typed functional languages such as Haskell, F#, and Idris (the last of which even supports *dependently-typed functional programming*, but I already said "finally" so we'll stop there). Of these, BQN supports first-class functional and function-level programming, allows but doesn't encourage pure functional programming, and does not support typed functional programming, as it is dynamically and not statically typed. @@ -26,26 +26,26 @@ What does functional programming in BQN look like? How is it different from the ### Working with roles -First, let's look at the basics: a small program that takes a function as its argument and result. The function `Lin` below gives a linear approximation to its function argument based on the values at 0 and 1. To find these two values, we call the argument as a function by using its uppercase spelling, `𝕏`. +First, let's look at the basics: a small program that has functions as its argument and result. The function `Lin` below gives a linear approximation to its function argument based on the values at 0 and 1. To find these two values, we call the argument as a function by using its uppercase spelling, `𝕏`. Lin ← { v0 ← 𝕏 0 v0 + ((𝕏 1) - v0) × ⊢ } -We can pass it the exponential function as an argument by giving it the name `Exp` and then referring to it in lowercase (that is, in a value role). The result is a train that adds 1 to *e*-1 times the argument. +We can pass it the exponential function as an argument by giving it the name `Exp` and then referring to it in lowercase (that is, in a subject role). The result is a train that adds 1 to *e*-1 times the argument. Exp ← ⋆ Lin exp (1 + (1.71828182845905 × ⊢)) -As with all functions, the result of `Lin` has a value role. To use it as a function, we give it a name and then use that name with an uppercase spelling. +As with all functions, the result of `Lin` has a subject role. To use it as a function, we give it a name and then use that name with an uppercase spelling. expLin ← Lin exp ExpLin 5 9.59140914229523 -A tricker but more compact method is to use the modifier `{𝔽}`, as the input to a modifier can have a value or function role but its output always has a function role. +A tricker but more compact method is to use the 1-modifier `{𝔽}`, as the input to a modifier can have a subject or function role but its output always has a function role. (Lin exp){𝔽} 5 9.59140914229523 @@ -69,12 +69,12 @@ Its call syntax is simpler as well. In other cases, however, the function versio ### Arrays of functions -It's very convenient to put a function in an array, which is fortunate because this is one of the most important uses of functions as values. Here's an example of an array of functions with a reduction applied to it, composing them together. +It's very convenient to put a function in an array, which is fortunate because this is one of the most important uses of functions as subjects. Here's an example of an array of functions with a reduction applied to it, composing them together. {𝕎∘𝕏}´ ⋆‿-‿(ט) ⋆∘(-∘(ט)) -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 value role (it's the result of the function `{𝕎∘𝕏}´`) and so must be defined with a lowercase name. +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 2 @@ -85,9 +85,9 @@ Another, and probably more common, use of arrays of functions is to apply severa ⟨√, 2⊸∾, ⊢-⋆⟩ {𝕎𝕩}¨ 9 [ 3 [ 2 9 ] ¯8094.083927575384 ] -The composition 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 value types there's no real difference: Choose returns the constant function `𝕗⊑𝕘`. +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. diff --git a/doc/group.md b/doc/group.md index b561cd7d..197e8977 100644 --- a/doc/group.md +++ b/doc/group.md @@ -10,7 +10,7 @@ Once defined, the old BQN Key (dyadic) is `⍷⊸⊐⊸⊔` and Group (monadic) ## Definition -Group operates on a numeric list of indices and a value array, treated as a list of its major cells, to produce a list of groups, each of which is a selection from the values. The indices and values have the same length, and each value cell is paired with the index at the same position. That index indicates the result group the value should go into, with an "index" of ¯1 indicating that it should be dropped and not appear in the result. +Group operates on a numeric list of 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. 0‿1‿2‿0‿1 ≍ "abcde" # Corresponding indices and values ┌ @@ -20,7 +20,7 @@ Group operates on a numeric list of indices and a value array, treated as a list 0‿1‿2‿0‿1 ⊔ "abcde" # Values grouped by index [ [ ad ] [ be ] [ c ] ] -For example, we might choose to group a list of words by length. Within each group, values maintain the ordering they had in the list originally. +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 @@ -40,7 +40,7 @@ For example, we might choose to group a list of words by length. Within each gro 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 ┌ @@ -52,7 +52,7 @@ If we'd like to ignore words of 0 letters, or more than 5, we can set all word l 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 gives the indices and the values grouped are the right argument's indices, that is, `↕≠𝕩`. +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, `↕≠𝕩`. ⥊˘ ⊔ 2‿3‿¯1‿2 ┌ @@ -66,7 +66,7 @@ Here, the index 2 appears at indices 0 and 3 while the index 3 appears at index ### 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 vector of numeric vectors, 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. +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. @@ -84,7 +84,7 @@ Here we split up a rank-2 array into a rank-2 array of rank-2 arrays. Along the Each group `i⊑𝕨⊔𝕩` is composed of the cells `j<¨⊸⊏𝕩` such that `i≢j⊑¨𝕨`. The groups retain their array structure and ordering along each argument axis. Using multidimensional Replicate we can say that `i⊑𝕨⊔𝕩` is `(i=𝕨)/𝕩`. -The monadic case works similarly: Group Indices always satisfies `⊔𝕩 ←→ 𝕩⊔↕≠⚇1 x`. 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. +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 diff --git a/doc/indices.md b/doc/indices.md index 382f7f80..0634087d 100644 --- a/doc/indices.md +++ b/doc/indices.md @@ -1,16 +1,16 @@ # Indices -One-dimensional arrays such as K lists or Python arrays have only one kind of index, a single number that refers to an element. For multidimensional arrays using the leading axis theory, there are several types of indexing that can be useful. Historically, nested APL designs have equivocated between these, which I believe can lead to subtle errors when programming. BQN focuses on single-number (depth 0) indices, which can refer to vector elements or array major cells (or more generally indexing along any particular axis). When using this kind of element indices, arrays are required to be vectors. Only two functions allow the use of vector element indices: Range (`↕`), which can accept a vector argument, and Pick (`⊑`), which uses the depth-1 arrays in its left argument as index scalars or vectors. +One-dimensional arrays such as K lists or Python arrays have only one kind of index, a single number that refers to an element. For multidimensional arrays using the leading axis theory, there are several types of indexing that can be useful. Historically, nested APL designs have equivocated between these, which I believe can lead to subtle errors when programming. BQN focuses on single-number (depth 0) indices, which can refer to list elements or array major cells (or more generally indexing along any particular axis). When using this kind of element index, indexed arrays are required to be lists. Only two functions allow the use of list element indices: Range (`↕`), which can accept a list argument, and Pick (`⊑`), which uses the depth-1 arrays in its left argument as index scalars or lists. Others use single-number indices to refer to cells. -The following functions take or return indices. Except where marked, the indices are in the result; this is by far the most common type of index use. `⊔` is given two rows as it falls into both cases. Note that in the result case, there is usually no possibility for the programmer to select the format of indices. Instead, the language should be carefully designed to make sure that the return type of indices is as useful as possible. +The following functions take or return indices. Except where marked, the indices are in the result; this is by far the most common type of index use. `⊔` is given two rows as it falls into both cases. Note that in the result case, there is usually no possibility for the programmer to select the format of indices. Instead, the language should be carefully designed to make sure that the kind of index returned is as useful as possible. | Monad | Dyad | Where | How |-------|------|---------|-------------------------- -| `↕` | | | Element scalar or vector +| `↕` | | | Element scalar or list | `/` | | | Element scalar | `⊔` | | | Element scalar | `⊔` | `⊔` | `𝕨`/`𝕩` | Along-axis scalar -| | `⊑` | `𝕨` | Element vector +| | `⊑` | `𝕨` | Element list | `⍋` | `⍋` | | Major cell scalar | `⍒` | `⍒` | | Major cell scalar | | `⊐` | | Major cell scalar @@ -18,29 +18,29 @@ The following functions take or return indices. Except where marked, the indices | | `⊏` | `𝕨` | Major cell or along-axis scalar | `⍉` | | | Axis scalar -Dyadic Transpose (`⍉`) takes an index into the right argument axes as its left argument, but since array shape is 1-dimensional, there is only one sensible choice for this, a single number. +Dyadic Transpose (`⍉`) uses indices into the right argument axes in its left argument, but since array shape is 1-dimensional, there is only one sensible choice for this, a single number. # Element indices -In general, the index of an element of an array is a vector whose length matches the array rank. It is also possible to use a number for an index into a vector, as the vector index is a singleton, but this must be kept consistent with the rest of the language. NARS-family APLs make the Index Generator (`↕` in BQN) return a numeric vector when the argument has length 1 but a nested array otherwise. This means that the depth of the result depends on the shape of the argument, inverting the typical hierarchy. BQN shouldn't have such an inconsistency. +In general, the index of an element of an array is a list whose length matches the array rank. It is also possible to use a number for an index into a list, as the list index is a singleton, but this must be kept consistent with the rest of the language. NARS-family APLs make the Index Generator (`↕` in BQN) return a numeric list when the argument has length 1 but a nested array otherwise. This means that the depth of the result depends on the shape of the argument, inverting the typical hierarchy. BQN shouldn't have such an inconsistency. -Functions `↕`, `/`, `⊔`, and `⊑` naturally deal with element indices. Each of these can be defined to use vector indices. However, this usually rules out the possibility of using scalar indices, which makes these functions harder to use both with generic array manipulation and with the major cell indices discussed in the next section. For this reason BQN restricts `⊔` and monadic `/` to use depth-0 indices, which comes with the requirement that the arguments to monadic `/` and `⊔`, and the result of monadic `⊔`, must be vectors. For dyadic `⊔` the depth-1 elements of the left argument are vectors of indices along axes of the result; see [the documentation](group.md#multidimensional-grouping). The restriction that comes from using single-number indices is that all axes must be treated independently, so that for example it isn't possible to group elements along diagonals without preprocessing. However, this restriction also prevents Group from having to use an ordering on vector indices. +Functions `↕`, `/`, `⊔`, and `⊑` naturally deal with element indices. Each of these can be defined to use list indices. However, this usually rules out the possibility of using scalar indices, which makes these functions harder to use both with generic array manipulation and with the major cell indices discussed in the next section. For this reason BQN restricts `⊔` and monadic `/` to use depth-0 indices, which comes with the requirement that the arguments to monadic `/` and `⊔`, and the result of monadic `⊔`, must be lists. For dyadic `⊔` the depth-1 elements of the left argument are lists of indices along axes of the result; see [the documentation](group.md#multidimensional-grouping). The restriction that comes from using single-number indices is that all axes must be treated independently, so that for example it isn't possible to group elements along diagonals without preprocessing. However, this restriction also prevents Group from having to use an ordering on list indices. -Unlike `/` and `⊔`, `↕` and `⊑` do use vector element indices. For `↕` this is because the output format can be controlled by the argument format: if passed a single number, the result uses single-number indices (so it's a numeric vector); if passed a vector, it uses vector indices and the result has depth 2 (the result depth is always one greater than the argument depth). For `⊑`, vector indices are chosen because `⊏` handles scalar indices well already. When selecting multiple elements from a vector, they would typically have to be placed in an array, which is equivalent to `⊏` with a numeric vector left argument. A single scalar index to `⊑` is converted to a vector, so it can be used to select a single element if only one is wanted. To select multiple elements, `⊑` uses each depth-1 array in the left argument as an index and replaces it with that element from the right argument. Because this uses elements as elements (not cells), it is impossible to have conformability errors where elements do not fit together. Ill-formed index errors are of course still possible, and the requirements on indices are quite strict. They must exactly match the structure of the right argument's shape, with no scalars or higher-rank arrays allowed. Single numbers also cannot be used in this context, as it would create ambiguity: is a one-element vector an index, or does it contain an index? +Unlike `/` and `⊔`, `↕` and `⊑` do use list element indices. For `↕` this is because the output format can be controlled by the argument format: if passed a single number, the result uses single-number indices (so it's a numeric list); if passed a list, it uses list indices and the result has depth 2 (the result depth is always one greater than the argument depth). For `⊑`, list indices are chosen because `⊏` handles scalar indices well already. When selecting multiple elements from a list, they would typically have to be placed in an array, which is equivalent to `⊏` with a numeric list left argument. A single scalar index to `⊑` is converted to a list, so it can be used to select a single element if only one is wanted. To select multiple elements, `⊑` uses each depth-1 array in the left argument as an index and replaces it with that element from the right argument. Because this uses elements as elements (not cells), it is impossible to have conformability errors where elements do not fit together. Ill-formed index errors are of course still possible, and the requirements on indices are quite strict. They must exactly match the structure of the right argument's shape, with no scalars or higher-rank arrays allowed. Single numbers also cannot be used in this context, as it would create ambiguity: is a one-element list an index, or does it contain an index? # Major cell indices -One of the successes of the [leading axis model](https://aplwiki.com/wiki/Leading_axis_theory) is to introduce a kind of index for multidimensional arrays that is easier to work with than vector indices. The model introduces [cells](https://aplwiki.com/wiki/Cell), where a cell index is a vector of any length up to the containing array's rank. General cell indices are discussed in the next section; first we introduce a special case, indices into major cells or ¯1-cells. These cells naturally form a list, so the index of a major cell is a single number. These indices can also be considered indices along the first axis, since an index along any axis is a single number. +One of the successes of the [leading axis model](https://aplwiki.com/wiki/Leading_axis_theory) is to introduce a kind of index for multidimensional arrays that is easier to work with than list indices. The model introduces [cells](https://aplwiki.com/wiki/Cell), where a cell index is a list of any length up to the containing array's rank. General cell indices are discussed in the next section; first we introduce a special case, indices into major cells or ¯1-cells. These cells naturally form a list, so the index of a major cell is a single number. These indices can also be considered indices along the first axis, since an index along any axis is a single number. Ordering-based functions `⍋`, `⍒`, `⊐`, and `⊒` only really make sense with major cell indices: while it's possible to order other indices as ravel indices, this probably isn't useful from a programming standpoint. Note that `⊐` only uses the ordering in an incidental way, because it's defined to return the *first* index where a right argument cell is found. A mathematician would be more interested in a "pre-image" function that returns the set of all indices where a particular value appears. However, programming usefulness and consistency with the other search functions makes searching for the first index a reasonable choice. -Only one other function—but an important one!—deals with cells rather than elements: `⊏`, cell selection. Like dyadic `↑↓↕⌽⍉` (depth 0) and `/⊔` (depth 1), Select allows either a simple first-axis case where the left argument has depth 1 or less (a depth-0 argument is automatically enclosed), and a multi-axis case where it is a vector of depth-1 elements. In each case the depth-1 arrays index into a single axis. +Only one other function—but an important one!—deals with cells rather than elements: `⊏`, cell selection. Like dyadic `↑↓↕⌽⍉` (depth 0) and `/⊔` (depth 1), Select allows either a simple first-axis case where the left argument has depth 1 or less (a depth-0 argument is automatically enclosed), and a multi-axis case where it is a list of depth-1 elements. In each case the depth-1 arrays index along a single axis. # General cell indices BQN does not use general cell indices directly, but it is useful to consider how they might work, and how a programmer might implement functions that use them in BQN if needed. The functions `/`, `⊔`, and `⊏` are the ones that can work with indices for multidimensional arrays but don't already. Here we will examine how multidimensional versions would work. -A cell index into an array of rank `r` is a numeric vector of length `l≤r`, which then refers to a cell of rank `r-l`. In BQN, the cell at index `i` of array `a` is `i<¨⊸⊏a`. +A cell index into an array of rank `r` is a numeric list of length `l≤r`, which then refers to a cell of rank `r-l`. In BQN, the cell at index `i` of array `a` is `i<¨⊸⊏a`. Because the shape of a cell index relates to the shape of the indexed array, it makes sense not to enclose cell indices, instead treating them as rows of an index array. A definition for `⊏` for depth-1 left arguments of rank at least 1 follows: replace each row of the left argument with the indexed cell of the right, yielding a result with the same depth as the right argument and shape `𝕨((¯1↓⊣)∾(¯1↑⊣)⊸↓)○≢𝕩`. diff --git a/doc/join.md b/doc/join.md index cb940699..31ec3876 100644 --- a/doc/join.md +++ b/doc/join.md @@ -1,6 +1,6 @@ # Join -Join (`∾`) is an extension of the monadic function [Raze](https://aplwiki.com/wiki/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 Unbox (`>`) does to Couple (`≍`): `a≍b` is `>a‿b` and `a∾b` is `∾a‿b`. While Unbox and Couple combine arrays (the elements of Unbox's argument, or the arguments themselves for Coups) along a new leading axis, Join and Join to combine them along the existing leading axis. Both Unbox and Join can also be called on a higher-rank array, causing Unbox to add multiple leading axes while Join combines elements along multiple existing axes. +Join (`∾`) is an extension of the monadic function [Raze](https://aplwiki.com/wiki/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 (`≍`): `a≍b` is `>a‿b` and `a∾b` is `∾a‿b`. 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). @@ -38,6 +38,6 @@ However, Join has higher-dimensional uses as well. Given a rank-`m` array of ran 3 3 3 3 4 4 5 5 5 5 5 ┘ -Join has fairly strict requirements on the shapes of its argument elements—although less strict than those of Unbox, which requires they all have identical shape. Suppose the argument to Join has rank `m`. Each of its elements must have the same rank, `n`, which is at least `m`. The trailing shapes `m↓⟜≢¨𝕩` must all be identical (the trailing shape `m↓≢∾𝕩` of the result will match these shapes as well). The other entries in the leading shapes need not be the same, but the shape of an element along a particular axis must depend only on the location of the element along that axis in the full array. For a vector argument this imposes no restriction, since the one leading shape element is allowed to depend on position along the only axis. But for higher ranks the structure quickly becomes more rigid. +Join has fairly strict requirements on the shapes of its argument elements—although less strict than those of Merge, which requires they all have identical shape. Suppose the argument to Join has rank `m`. Each of its elements must have the same rank, `n`, which is at least `m`. The trailing shapes `m↓⟜≢¨𝕩` must all be identical (the trailing shape `m↓≢∾𝕩` of the result will match these shapes as well). The other entries in the leading shapes need not be the same, but the shape of an element along a particular axis must depend only on the location of the element along that axis in the full array. For a list argument this imposes no restriction, since the one leading shape element is allowed to depend on position along the only axis. But for higher ranks the structure quickly becomes more rigid. -To state this requirement more formally in BQN, we say that there is some vector `s` of length vectors, so that `(≢¨s)≡≢𝕩`. We require element `i⊑𝕩` to have shape `i⊑¨s`. Then the first `m` axes of the result are `+´¨s`. +To state this requirement more formally in BQN, we say that there is some list `s` of lists of lengths, so that `(≢¨s)≡≢𝕩`. We require element `i⊑𝕩` to have shape `i⊑¨s`. Then the first `m` axes of the result are `+´¨s`. diff --git a/doc/logic.md b/doc/logic.md index 0e38fb1d..638bdace 100644 --- a/doc/logic.md +++ b/doc/logic.md @@ -14,7 +14,7 @@ We define: And ← × Or ← ×⌾¬ -Note that `¬⁼ ←→ ¬`, since the first added 1 will be negated but the second won't; the two 1s cancel leaving two subtractions, and `-⁼ ←→ -`. An alternate definition of Or that matches the typical formula from probability theory is +Note that `¬⁼ ←→ ¬`, since when applying `¬` twice the first added 1 will be negated but the second won't; the two 1s cancel leaving two subtractions, and `-⁼ ←→ -`. An alternate definition of Or that matches the typical formula from probability theory is Or ← +-× diff --git a/doc/transpose.md b/doc/transpose.md index 006f0de6..73653e0e 100644 --- a/doc/transpose.md +++ b/doc/transpose.md @@ -42,7 +42,7 @@ But, reading in ravel order, the argument and result have exactly the same eleme ┘ ┘ -To exchange multiple axes, use the Power operator. 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 `⍉`). +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 [ 5 6 2 3 4 ] @@ -51,7 +51,7 @@ To exchange multiple axes, use the Power operator. Like Rotate, a negative power 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 operator 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. +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 [ 2 3 5 6 4 ] @@ -104,7 +104,7 @@ Finally, it's worth noting that, as monadic Transpose moves the first axis to th Here we define the two valences of Transpose more precisely. -A non-array right argument to Transpose is always boxed to get a scalar array before doing anything else. +A non-array right argument to Transpose is always enclosed to get a scalar array before doing anything else. Monadic transpose is identical to `(≠∘≢-1˜)⊸⍉`, except that for scalar arguments it returns the array unchanged rather than giving an error. diff --git a/doc/windows.md b/doc/windows.md index d4165d62..82397a42 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -1,6 +1,6 @@ # Windows -In BQN, it's strongly preferred to use functions, and not operators (modifiers and compositions), for array manipulation. Functions are simpler as they have fewer moving parts. They are more concrete, since the array results can always be viewed right away. They are easier to implement with reasonable performance as well, since there is no need to recognize many possible function operands as special cases. +In BQN, it's strongly preferred to use functions, and not modifiers, for array manipulation. Functions are simpler as they have fewer moving parts. They are more concrete, since the array results can always be viewed right away. They are easier to implement with reasonable performance as well, since there is no need to recognize many possible function operands as special cases. 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. @@ -24,11 +24,11 @@ You can take a slice of an array `𝕩` that has length `l` and starts at index 5↑2↓"abcdefg" [ cdefg ] -Windows differs from Prefixes and Suffixes in that it doesn't add a layer of nesting (it doesn't box each slice). This is possible because the slices have a fixed size. +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 `3‿4` array. For convenience, we will box each slice. Note that slices always have the same rank as the argument array. +The above description applies to a higher-rank right argument. As an example, we'll look at two-row slices of a shape `3‿4` 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" ┌ |
