diff options
| author | Marshall Lochbaum <mwlochbaum@gmail.com> | 2022-06-26 21:00:25 -0400 |
|---|---|---|
| committer | Marshall Lochbaum <mwlochbaum@gmail.com> | 2022-06-26 21:00:25 -0400 |
| commit | b6185d5029e2adcc721c0cc2097f591d9a09f135 (patch) | |
| tree | bf777353ed2a9b28d8b1577c5f36b68605240375 /doc | |
| parent | c618ade174cc2b4e428457751ad8dd01130c2239 (diff) | |
I am in editing stepped in so far that, should I wade no more, returning were as tedious as go o'er.
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/arithmetic.md | 30 | ||||
| -rw-r--r-- | doc/array.md | 18 | ||||
| -rw-r--r-- | doc/arrayrepr.md | 20 | ||||
| -rw-r--r-- | doc/assert.md | 13 | ||||
| -rw-r--r-- | doc/based.md | 2 | ||||
| -rw-r--r-- | doc/birds.md | 2 | ||||
| -rw-r--r-- | doc/block.md | 56 | ||||
| -rw-r--r-- | doc/choose.md | 8 | ||||
| -rw-r--r-- | doc/compose.md | 6 |
9 files changed, 89 insertions, 66 deletions
diff --git a/doc/arithmetic.md b/doc/arithmetic.md index c3e17bf8..e51fc53e 100644 --- a/doc/arithmetic.md +++ b/doc/arithmetic.md @@ -16,16 +16,16 @@ Summary of other differences from APL: BQN of course supports the elementary functions taught in schools everywhere: -Symbol | Dyad | Monad --------|----------|------- -`+` | Add | *(Conjugate)* -`-` | Subtract | Negate -`Γ` | Multiply | Sign -`Γ·` | Divide | Reciprocal -`β` | Power | Exponential -`β` | Root | Square root +Symbol | Dyad | Monad | default `π¨` +-------|----------|---------------|:----------: +`+` | Add | *(Conjugate)* | β +`-` | Subtract | Negate | 0 +`Γ` | Multiply | Sign | β +`Γ·` | Divide | Reciprocal | 1 +`β` | Power | Exponential | *e* +`β` | Root | Square root | 2 -The dyadic functions should all be familiar operations, and most likely you are familiar with the symbols `+-ΓΓ·β`. In fact the large `Γ` and `Γ·` might strike you as a regression to early school years, before division was written vertically and multiplication with a simple dot or no symbol at all (BQN reserves the distinction of having no symbol for application and composition). Like these, raising to a power or exponentiation is made regular by giving it the symbol `β`βa true Unicode star and *not* an asterisk. The Root function `β` is also modified to be a binary function, which raises `π©` to the power `Γ·π¨`. In ASCII programming languages `Γ`, `Γ·`, and `β` are often written `*`, `/`, and `^` or `**`. +The dyadic (two-argument) functions should all be familiar operations, and most likely you know the symbols `+-ΓΓ·β`. In fact the large `Γ` and `Γ·` might strike you as a regression to early school years, before division was written vertically and multiplication with a simple dot or no symbol at all. Like these, raising to a power or exponentiation is made regular by giving it the symbol `β`βa true Unicode star and *not* an asterisk. The Root function `β` is also modified to be a binary function, which raises `π©` to the power `Γ·π¨`. In ASCII programming languages `Γ`, `Γ·`, and `β` are often written `*`, `/`, and `^` or `**`. 2 + 3βΏ1βΏ0βΏ5 @@ -39,7 +39,7 @@ The dyadic functions should all be familiar operations, and most likely you are 4 β 81 -Each of these functions also has a meaning with only one argument, although in mathematics only `-` does. The relationship of negation to addition is extended to division (relative to multiplication) as well, so that `Γ·π©` gives the reciprocal `1Γ·π©` of its argument. Power (`β`) is also extended with a default left argument of [Euler's number](https://en.wikipedia.org/wiki/E_(mathematical_constant)) *e*. The default left argument for Root is 2, giving the well-known Square Root. +Each of these functions also has a meaning with only one argument, although mathematics only defines `-` in this way. The relationship of negation to addition is extended to division (relative to multiplication) as well, so that `Γ·π©` gives the reciprocal `1Γ·π©` of its argument. Power (`β`) is also extended with a default left argument of [Euler's number](https://en.wikipedia.org/wiki/E_(mathematical_constant)) *e*. The default left argument for Root is 2, giving the well-known Square Root. - 6 @@ -51,7 +51,7 @@ Each of these functions also has a meaning with only one argument, although in m Take note of the difference between the function `-`, and the "high minus" character `Β―`, which is a part of [numeric notation](syntax.md#constants). Also shown is the number `β`, which BQN supports along with `Β―β` (but depending on implementation BQN may or may not keep track of `Β―0`. Integer optimization loses the distinction so it's best not to rely on it). -The logarithm is written with Undo: `ββΌ`. As with Power, the default base is *e*, giving a natural logarithm. +The logarithm is written with [Undo](undo.md): `ββΌ`. As with Power, the default base is *e*, giving a natural logarithm. ββΌ 10 @@ -122,7 +122,7 @@ Now the monadic function symbols resemble those used in mathematics. In the case | Β―ββΏΒ―6βΏ0βΏ2 -Floor (`β`) returns the largest integer less than or equal to the argument, and Ceiling (`β`) returns the smallest one greater than or equal to it. For this purpose `Β―β` and `β` are treated as integers, so that the floor or ceiling of an infinity is itself. Absolute value removes the argument's sign by negating it if it is less than 0, so that its result is always non-negative. +Floor (`β`) returns the largest integer less than or equal to the argument, and Ceiling (`β`) returns the smallest one greater than or equal to it. For this purpose `Β―β` and `β` are treated as integers, so that the floor or ceiling of an infinity is itself. Absolute value removes the sign of `π©` by negating it if it's less than 0, so that its result is always non-negative. Minimum (`β`) returns the smaller of its two arguments, and Maximum (`β`) returns the larger. These functions are loosely related to Floor and Ceiling in their use of comparison, and can be defined similarly: for example, the minimum of two numbers is the largest number less than or equal to both of them. To take the minimum or maximum of an entire list, use a [fold](fold.md). @@ -130,7 +130,7 @@ Minimum (`β`) returns the smaller of its two arguments, and Maximum (`β`) re β½βΈβ β8 -Modulus (`|`) is similar to the modular division operation written `%` in C-like languages, but it takes the arguments in the opposite order, and differs in its handling of negative arguments. It's defined to be `{π©-π¨Γβπ©Γ·π¨}`, except that the multiplication should always return 0 if its right argument is 0, even if `π¨` is infinite. +Modulus (`|`) is similar to the modular division operation written `%` in C-like languages, but it takes the arguments in the opposite order, and differs in its handling of negative arguments. It's the same computation as `{π©-π¨Γβπ©Γ·π¨}` but probably has better precision. 3 | β8 @@ -171,11 +171,11 @@ Arithmetic primitives act as though they are given [depth](depth.md#the-depth-mo Γ βΛβ¨Β―8,Β―9β©βΏβ¨β¨2,0β©,4,5β© -With two arguments many combinations are possible. Arrays of equal shape are matched element-wise, and an atom is matched to every element of an array. +With two arguments, many combinations are possible. Arrays of equal shape are matched element-wise, and an atom is matched to every element of an array. 10βΏ20βΏ30 + 5βΏ6βΏ7 - 10 Γ 4βΏ3βΏ2β6βΏ7βΏ8 + 10 Γ [4βΏ3βΏ2,6βΏ7βΏ8] Arrays with different ranks can also be paired: they are matched by [leading axis agreement](leading.md#leading-axis-agreement). This means that one shape must be a prefix of the other, and elements of the lower-rank array are repeated to match up with cells of the higher-rank one. diff --git a/doc/array.md b/doc/array.md index 873778c9..19fe7fe7 100644 --- a/doc/array.md +++ b/doc/array.md @@ -6,9 +6,9 @@ As BQN is an array language, it's often helpful to understand what an array is w In BQN, as in APL, arrays are multidimensional, instead of strictly linear. Languages like Python, Javascript, or Haskell offer only one-dimensional arrays with `[]` syntax, and typically represent multidimensional data with nested arrays. Multidimensional arrays have fundamental differences relative to this model. -BQN's arrays are immutable, meaning that an array is entirely defined by its attributes, and there is no way to modify an existing array, only to produce another array that has changes relative to it. As a result, an array can never contain itself, and arrays form an inductive type. BQN's [mutable](lexical.md#mutation) types are operations and namespaces. +BQN's arrays are immutable, meaning that an array is entirely defined by its attributes, and there's no way to modify an existing array, only to produce another array that has changes relative to it. As a result, an array can never contain itself. BQN's [mutable](lexical.md#mutation) types are operations and namespaces. -An array might also have a [fill element](fill.md) that captures some structural information about its elements and is used by a few operations. The fill, as an inferred property, isn't considered to truly be part of the array but is instead some information about the array that the interpreter keeps track of. So it's out of scope here. +An array might also have a [fill element](fill.md) that captures some structural information about its elements and is used by a few operations. As an inferred property, the fill isn't considered to truly be part of the array, but is instead some information about it that the interpreter keeps track of. So it's out of scope here. <!--GEN xt β βΎΛ Highlightββ’ReprΒ¨ xv β 3βΏ2βΏ6β₯ΓΛ2+3Γβ5 @@ -87,7 +87,7 @@ The array also needs to be complete. Every elementβevery combination of positi ## Ordering and indices -To finish this definition of an array we also need to nail down the idea of a position. The positions along one dimension can't be labelled in any way, but they have a linear ordering (mathematically speaking, a [total order](https://en.wikipedia.org/wiki/Total_order): out of any two different positions one comes earlier and the other later). BQN keeps track of this order: for example, when we [join](join.md) two arrays it places positions in `π¨` before those of `π©` and otherwise maintains the original ordering. +To finish this definition of an array we also need to nail down the idea of a position. The positions along one dimension can't have labels attached to them, but they have a linear ordering (mathematically speaking, a [total order](https://en.wikipedia.org/wiki/Total_order): out of any two different positions one comes earlier and the other later). BQN keeps track of this order: for example, when we [join](join.md) two arrays it places positions in `π¨` before those of `π©` and otherwise maintains the original orderings. "before" βΎ "after" @@ -103,19 +103,19 @@ The total number of elements in an array is its **bound**, and can be found usin ## Elements -Any BQN value can be used as an array element, including another array (BQN, as a dynamically-typed language, doesn't restrict the types that can be used in one context without a good reason). However, BQN arrays are restricted relative to other array models. Frameworks like NumPy or Julia have mutable arrays, so that the value of an element can be changed after the array is created. This allows an array to be its own element, by creating an array and then inserting it into itself. This would be unnatural in BQN, where an array can only be formed from elements that already exist. In BQN only operations and namespaces are [mutable](lexical.md#mutation). +Any BQN value can be used as an array element, including another array (BQN, as a dynamically-typed language, doesn't restrict the types that can be used in one context without a good reason). However, BQN arrays are in some sense restricted relative to other array models. Frameworks like NumPy or Julia have mutable arrays, so that the value of an element can be changed after the array is created. This allows an array to be its own element, by creating an array and then inserting it into itself. This would be unnatural in BQN, where an array can only be formed from elements that already exist. In BQN only operations and namespaces are [mutable](lexical.md#mutation). -An array with no elements (a bound of 0) is called **empty**. These arrays can cause problems when a property should be computed from the elements of an array, like the sum `+Β΄π©` or shape of the [merged](couple.md#merge-and-array-theory) array `>π©`. BQN has two mechanisms to make these cases work better. First, reductions like `+Β΄` have [identity values](fold.md#identity-values) for certain functions, so that `+Β΄β¨β©` is `0` for example. Second, every array might have a [fill element](fill.md), a special "typical element" for the array. Functions like Merge use this element's structure to determine the result shape when there are no actual elements to be used. +An array with no elements (a bound of 0) is called **empty**. These arrays can cause problems when a property should be computed from the elements of an array, like the sum `+Β΄π©` or shape of the [merged](couple.md#merge-and-array-theory) array `>π©`. BQN has two mechanisms to make these cases work better. First, Fold (`Β΄`) and Insert (`Λ`) have [identity values](fold.md#identity-values) for certain functions, so that `+Β΄β¨β©` is `0` for example. Second, every array might have a [fill element](fill.md), a special "typical element" for the array. Functions like Merge use this element's structure to determine the result shape when there are no actual elements to be used. ## Cells -The contents of an array are its elements, but it also makes sense to split up an array into subarrays of elements called cells. The most important kind of cell, a **major cell** consists of all the elements that have indices beginning with some particular index `i`. For this to make sense, `i` must be between `0` and the length `l` of the array's first axis, so that there are `l` major cells each identified by an index. +The contents of an array are its elements, but it also makes sense to split up an array into subarrays of elements called cells. The most important kind of cell, a **major cell**, consists of all the elements whose indices begin with some particular index `i`. For this to make sense, `i` must be between `0` and the length `n` of the array's first axis, so that there are `n` major cells each identified by an index. 2βΏ3βΏ4 Γβ 1βΏ5βΏ8βΏ11 1 β 2βΏ3βΏ4 Γβ 1βΏ5βΏ8βΏ11 # Major cell 1 -A major cell still has an array structure: it retains all the axes of the original array other than the first. So it has its own major cells, identified by the index `i` of the original major cell and `j` within it. These are also cells of the original array. Generalizing, a **cell** with index list `l` is defined to be the array of all elements whose indices begin with `l`. In an array with rank `n`, the cell rank is `n-β l`, and cells grouped using this rank. An `n`-cell mst have an empty cell index, so that it includes all elementsβit's the entire array! An `n-1` cell, also called a Β―1-cell, is a major cell. A 0-cell has an index of length `n`, and contains a single element. +A major cell still has an array structure: it retains all the axes of the original array other than the first. So it has its own major cells, identified by the index `i` of the original major cell and `j` within it. These are also cells of the original array. Generalizing, a **cell** with index list `l` is defined to be the array of all elements whose indices begin with `l`. In an array with rank `r`, the cell rank is `r-β l`, and cells grouped using this rank. An `r`-cell must have an empty cell index, so that it includes all elementsβit's the entire array! An `r-1` cell, also called a Β―1-cell, is a major cell. A 0-cell has an index of length `r`, and contains a single element. Cells are the center of the [leading axis model](leading.md) used to structure many array primitives. @@ -123,9 +123,9 @@ Cells are the center of the [leading axis model](leading.md) used to structure m Summarizing, the values needed to define an array are its rank (the number of axes), its shape (the number of positions along each axis), and the value of each element (that is, at each combination of positions). Two arrays [match](match.md) when all these values match. -If the rank is considered to be part of the shape, as it is when the shape is a BQN list, then the array is defined by its shape and element listβfrom [deshape](reshape.md). +If the rank is considered to be part of the shape, as it is when the shape is a BQN list, then the array is defined by its shape, and element list as returned by [deshape](reshape.md). -Here's a somewhat informal mathematical take. Given a set of possible element values `T`, a *list* of `T` of length `l` is a map from natural numbers less than `l` to `T`. An array is a rank `r`, along with a list `s` of natural numbers of length `r`, and a map from lists of natural numbers `i` that satisfy `i(j) < s(j)` for all natural numbers `j<r` to BQN values. Arrays are an inductive type, so that an array can only be defined using elements that already exist. As a result an array's elements are always values of lesser complexity and selecting one element of an array, then an element of that element, and so on, must eventually reach a non-array. +Here's a somewhat informal mathematical take. Given a set of possible element values `T`, a *list* of `T` of length `l` is a map from natural numbers less than `l` to `T`. An array is a rank `r`, along with a list `s` of natural numbers of length `r`, and a map from lists of natural numbers `i` that satisfy `i(j) < s(j)` for all natural numbers `j<r` to BQN values. Arrays are an inductive type, so that an array can only be defined using elements that already exist. As a result, an array's elements are always values of lesser complexity than it: selecting one element of an array, then an element of that element, and so on, must eventually reach a non-array. ## Why arrays? diff --git a/doc/arrayrepr.md b/doc/arrayrepr.md index ca2f3f86..205c4a44 100644 --- a/doc/arrayrepr.md +++ b/doc/arrayrepr.md @@ -2,13 +2,13 @@ # Array notation and display -This page documents ways arrays are represented in BQN: the notation you can use to write them and the way the REPL displays them. +This page documents ways [arrays](array.md) are represented in BQN: the notation you can use to write them and the way the REPL displays them. Array display is a feature of a BQN environment such as a REPL. You can also access it with `β’Fmt`, which takes a value and returns a string indicating how it would be formatted. Array notation is of course part of BQN source code, but you can also go from an array to one possible source code for it using the similar system function `β’Repr`. ## Array display -Although it's really part of the language environment and not BQN itself, let's look at display first so it's clear what arrays we're talking about later on. The BQN REPL prints arrays in a way that's meant to unambiguously show the structure and data, but doesn't correspond to BQN source code. A few examples are given below; of course, displays like this appear all over the documentation. +Although it's really part of the language environment and not BQN itself, let's look at display first so it's clear what arrays we're talking about later on. A BQN session prints arrays in a way that's meant to unambiguously show the structure and data, but doesn't correspond to BQN source code. A few examples are given below; of course, displays like this appear all over the documentation. β 3βΏ4 # Array of lists @@ -26,7 +26,7 @@ Those top-left and bottom-right corners are a distinctive part of BQN's display, β¨2,"xy"β©ββ¨2βΏ2β₯"abcd",4β© # Nested 2Γ2 array -The lack of extra separation is to make it clear that the corners enclose the array rather than any of its elements (elements are still distinguishable becase an individual element won't contain whitespace except maybe between quotes). Now every set of corners indicates one array. This is a good fit for the [based array model](based.md), where data doesn't have to be in an array. +The lack of extra separation is to make it clear that the corners enclose the whole array rather than any of its elements (elements are still distinguishable becase an individual element won't contain whitespace, except maybe between quotes). Every set of corners indicates one array. This is a good fit for the [based array model](based.md), where data doesn't have to be in an array. #### Rank indicator @@ -58,7 +58,7 @@ The pattern continues: 3-cells are separated by 2 spaces, 4-cells by 3, and so o #### Empty arrays -The top-left corner can show the rank of an array but not its shape; the shape must be seen from the data. An empty array has no data, and it's hard to tell shape from a bunch of blank space. In general, an empty array is printed as `βshape`. An empty list is shown using brackets `β¨β©`, which are discussed in the next section. +The top-left corner can show the rank of an array but not its shape; the shape must be seen from the data. An empty array has no data, and it can be impossible to tell shape from a bunch of blank space. So an empty array is usually printed as `βshape`. An empty list is shown using brackets `β¨β©`, which are discussed in the next section. βΒ¨ β¨0βΏ4, 3βΏ0βΏ1, 2βΏ0βΏ0, 0β© @@ -82,13 +82,13 @@ The second is for lists with simple enough elements, which are displayed on one "" -This case also covers empty lists, which are shown as `β¨β©`. This includes an empty string, as the only difference between an empty string and any other empty list is its fill element and array displays don't depend on the fill. +This case also covers empty lists, which are shown as `β¨β©`. This includes an empty string: the only difference between an empty string and any other empty list is its fill element, and array displays don't depend on the fill. ## Array literals *The tutorial section [here](../tutorial/list.md#list-notation) also covers this topic.* -There are three kinds literal notation for lists: strings, list notation, and stranding. Strings indicate character lists (with space for the [fill](fill.md)) and the other two can combine any sequence of elements. Additionally, there's a square bracket notation that can form higher-rank arrays. +Now it's time to discuss ways to write arrays in a BQN program. There are three kinds literal notation for lists: strings, list notation, and stranding. Strings indicate character lists (with space for the [fill](fill.md)) and the other two can combine any sequence of elements. Additionally, there's a square bracket notation that can form higher-rank arrays. ### Strings @@ -104,11 +104,11 @@ Even special characters like a newline can appear in a string literal, so that s **List notation** uses angle brackets `β¨β©`. The contents are structurally identical to those of a [block](block.md), that is, a list of expressions [separated](syntax.md#separators) by `,` or `β` or newlines. Unlike a block, a list doesn't need to have any expressions: `β¨β©` or `β¨ββ©` or `β¨,,β,β©` will create an empty list. Other differences are that a list doesn't introduce a new [scope](lexical.md) and all of the expressions have to result in a value, not [Nothing](expression.md#nothing) (`Β·`). -Entries in a list are evaluated in source order, and the value will be the list of those results. The list has a subject role, even if it contains expressions with other roles. Any value can be an element. +Entries in a list are evaluated in source order, and the value will be the list of those results. The list has a subject [role](expression.md#syntactic-role), even if it contains expressions with other roles. Any value can be an element. β¨@, βΛ, β"abc"β© -BQN's separator rules give list notation a very flexible structure. You can put all the elements on one line or spread them across lines, with the option of adding blank lines between elements. A separator at the end of a line is never needed but leading and trailing separators are allowed. +BQN's separator rules give list notation a very flexible structure. You can put all the elements on one line or spread them across lines, with the option of adding blank lines between elements. A separator at the end of a line is never needed, but leading and trailing separators are allowed. β¨ "e0", "e1" @@ -127,11 +127,11 @@ Higher-rank arrays can be written with `[]`, an **array notation** that indicate [2βΏ3, 4βΏ1, 0βΏ5] -This syntax doesn't work for creating rank 0 arraysβuse [Enclose](enclose.md) `<` for theseβor empty arrays. The notation `[]` would be ambiguous, so it's not allowed (although it can be used for destructuring, which works with an existing array). To create a specific empty array, [Reshape](reshape.md) (`β₯`) is probably the best approach. +This syntax doesn't work for creating rank 0 arraysβuse [Enclose](enclose.md) `<` for theseβor empty arrays. The notation `[]` would be ambiguous, so it's not allowed (although it can be used for destructuring, which works with an existing array). To create an empty array with a specific shape, [Reshape](reshape.md) (`β₯`) is probably the best approach. ### Strands -**Strand notation** is another way to write lists of length two or more. The elements are connected with the ligature character `βΏ`. It has a precedence lower than the [namespace](namespace.md) dot but higher than anything else other than paired brackets `()`, `{}`, and `β¨β©`, so compound elements generally need to be placed in parentheses. Expressions joined by ligatures behave exactly the same as those in list notation: they are evaluated in order and placed in a list. +**Strand notation** is another way to write lists of length two or more. The elements are connected with the ligature character `βΏ`. It has a precedence higher than anything else other than the [namespace](namespace.md) dot `.` and of course paired brackets `()`, `{}`, and `β¨β©`. This means complicated elements generally need to be placed in parentheses. Expressions joined by ligatures behave exactly the same as those in list notation: they are evaluated in order and placed in a list. +βΏΒ΄βΏββΏΓ diff --git a/doc/assert.md b/doc/assert.md index 14e4cf72..83d8570c 100644 --- a/doc/assert.md +++ b/doc/assert.md @@ -9,23 +9,30 @@ BQN provides some simple facilities for dealing with errors. Errors are an unusu BQN takes the position that errors exist to indicate exceptional conditions that the developer of a given program didn't expect. However, the types of errors that BQN naturally checks for, such as mismatched shapes in Couple (`β`), aren't always enough to detect exceptional conditions. Issues like numeric values that don't make physical sense will slip right through. BQN makes it easy for a programmer to check for these sorts of problems by building in the primitive Assert, written `!`. This function checks whether `π©` matches `1`: if it does, then it does nothing and returns `π©`, and otherwise it gives an error. ! 2=2 # Passed + ! 2=3 # Failed To pass, the right argument must be exactly the number `1`; any other value causes an error. For example, an array of `1`s still causes an error; use `β§Β΄β₯` to convert a boolean array to a single boolean that indicates whether all of its values are true. ! (β§=β¨βΎΒ¬)βΛ β2 + ! β§Β΄β₯ (β§=β¨βΎΒ¬)βΛ β2 -Assert can take a left argument, which gives a message to be associated with the error. It's typical to use a string for the left argument in order to display it to the programmer, but the left argument can be any value. +Assert can take a left argument, which gives a message to be associated with the error. It's typical to use a string for `π¨` in order to display it to the programmer, but `π¨` can be any value. "Message" ! 0 + β¨β,"abc",Λβ© ! '0' +In the 1-argument case, `π©` is used for the error message if it's not `1`. So an unconditional error can also be written this way: + + ! "Message" + ### Computing the error message on demand Because the left argument to a function is always computed before the function is called, Assert [doesn't let you](../commentary/problems.md#assert-has-no-way-to-compute-the-error-message) compute the error message only if there's an error. This might be a problem if the error message computation is slow or has side effects. There are a few ways to work around the issue: -- Handle errors with ordinary if-then logic (perhaps using [control structures](control.md)). This is probably the best path for user-facing applications where displaying an error goes through the user interface. -- Write a function `Message` to compute the message, and call `π¨ MessageβΈ!β(1βΈβ’) π©` or similar instead of `!`. +- Handle bad inputs with ordinary if-then logic (perhaps using [control structures](control.md)), not errors. This is probably the best path for user-facing applications where BQN's normal error display isn't wanted. +- Write a function `Message` to compute the message, and call `!βMessageβ(1βΈβ’) π©` or similar instead of `!`. - If the error will be caught elsewhere in the program, use a closure for the message and evaluate it when caught. With a function `Message` as above, `message ! π©` works, and `{β¦}ΛβΈ! π©` is a convenient syntax for block functions. ## Catch diff --git a/doc/based.md b/doc/based.md index f7bd2431..26c8b212 100644 --- a/doc/based.md +++ b/doc/based.md @@ -16,7 +16,7 @@ If you're an array programmer then I have bad news for you. My thesis here is th 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 six [types](types.md) of atom, which together with the array type give the seven types a value can have in BQN. Based means being yourself, and an atom's *not* an array. -An atom has [depth](depth.md) 0, and doesn't inherently have a shape. However, primitives that expect an array promote atoms by [enclosing](enclose.md) 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](shape.md) 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. +An atom has [depth](depth.md) 0, and doesn't inherently have a shape. However, primitives that expect an array will promote an atom by [enclosing](enclose.md) to get a rank-0, or *unit*, array that contains it (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](shape.md) 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. diff --git a/doc/birds.md b/doc/birds.md index 71a59d3b..77811bf8 100644 --- a/doc/birds.md +++ b/doc/birds.md @@ -2,7 +2,7 @@ # BQN for birdwatchers -Some people consider it reasonable to name [combinators](primitive.md#modifiers) after types of birds. [Here's](https://blog.lahteenmaki.net/combinator-birds.html) one compendium of such names, and [another](https://wiki.xxiivv.com/site/ornithodex.html) that lacks its obsessive completeness but makes up for it with rambling accounts of imagined forests. There is something wrong with these people. Some of these birds are not even real. "Quixotic bird"? Have you not heard of a quail? Nonetheless, I don't judge such afflicted souls (certainly not publicly), and have provided this translation table to explain BQN in terms they can understand. +Some people consider it reasonable to name [combinators](primitive.md#modifiers) after types of birds. [Here's](https://blog.lahteenmaki.net/combinator-birds.html) one compendium of such names, and [another](https://wiki.xxiivv.com/site/ornithodex.html) that lacks its obsessive completeness but makes up for it with [rambling accounts](https://wiki.xxiivv.com/site/logic.html) of imagined forests. There is something wrong with these people. Some of these birds are not even real. "Quixotic bird"? Have you not heard of a quail? Nonetheless, I don't judge such afflicted souls (certainly not publicly), and have provided this translation table to explain BQN in terms they can understand. | BQN | Bird 1 | 1 | Bird 2 | 2 | | :-----: | -------- | -------- | ------------ | ------------------------------ | diff --git a/doc/block.md b/doc/block.md index 8cdaed9c..823007f1 100644 --- a/doc/block.md +++ b/doc/block.md @@ -2,14 +2,16 @@ # 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. An important aspect of organization is [namespaces](namespace.md), which are created with blocks but not discussed on this page. +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. One organizing tool not discussed here is [namespaces](namespace.md), which are created with blocks but have their own page. Programming without blocks (only recommended at the small scale) is called [tacit](tacit.md) programming. 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 Γ{π©π½π©} 4 -Because they use [lexical scoping](lexical.md), 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. +A block [header](#block-headers) is written before a `:` and describes the block type, and what inputs it accepts. A block can be split into [multiple bodies](#multiple-bodies) using `;`s, so that each handles different cases. A [predicate](#predicates), written with `?`, can test an arbitrary condition to refine these cases. + +Because they use [lexical scoping](lexical.md), blocks also 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. aβbβ"outer" { aβ"inner" β aβΏb } @@ -21,7 +23,7 @@ In the simplest case a block is just a list of statements, which are executed to updown β { upββ5 β downββ½up β upβΎdown } updown -An immediate block is only ever evaluated once, and can't be used for control flow in a program. Including special names in a headerless block lets us define functions and modifiers, which have a broader range of uses. All special names are listed below: +An immediate block is only ever evaluated once, and can't be used for control flow in a program. Special names can be used to define [functions and modifiers](ops.md), which have a broader range of uses. All special names are listed below: | Lowercase | Uppercase | Meaning |-----------|-----------|--------- @@ -32,27 +34,30 @@ An immediate block is only ever evaluated once, and can't be used for control fl | `π` | `πΎ` | Right [operand](#operands) | `π£` | none | Modifier [self-reference](#self-reference) -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. This allows a modifier to be applied to a special name with no spacing, as in `π_m`, where it couldn't be with ordinary names. +Most special names have a lowercase form for a subject [role](expression.md#syntactic-role) and uppercase for a function role. But `π£` is sort of a "more special" character, as we'll discuss below. The special names other than `π£` are single characters that don't attach to other letters, allowing `π½π©` or `π_m` to work without spaces; `π£` is always modifier-valued, so it ought to attach to underscores. ### 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. Having either of these names turns 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. Their values can be changed like ordinary variables. {'c'=π©} "abcd" + { π©+β©2 β 0βπ© } 3 - 4 { β¨π©β-π¨β© } 5 + + 4 { β¨π©,-π¨β© } 5 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](expression.md#nothing), or `Β·`. This is the only time a variable can ever be Nothing, as an assignment such as `vβΒ·` is not allowed. 3 { (2Γπ¨)-π© } 1 { (2Γπ¨)-π© } 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. +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](train.md). "abc" { (π¨ββ½) π© } "def" { (π¨ββ½) π© } "def" -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. +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](../problems.md#nothing--interacts-strangely-with-before-and-after) is that `βΈ` and `β` don't work the way you might expect. + { π¨ ββΈ- π© } 5 @@ -79,22 +84,22 @@ The distinction between an immediate and deferred modifier only matters inside t ### Self-reference -If a block is assigned a name after it is created, this name can be used for recursion: +If a block is assigned a name after it's created, this name can be used for recursion: Fact β { π© Γ (0βΈ<)βΆ1βΏFact π©-1 } Fact 7 (ΓΒ΄1+β) 7 # There's often a simpler solution than recursion -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. +This is somewhat unsatisfying because the name is external to the function being defined, but the definition shouldn'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 -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. +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's usually done with a second `factorial_helper` function in elementary Scheme (BQN doesn't optimize tail recursion though; it's just shown here as an example). Fact_mod β 1 { (0βΈ<)βΆβ¨π, (πΓπ©)_π£β© π©-1 } Fact_mod 7 -Because `π£` only ever refers to a 1-modifier or 2-modifer, it can never make sense to refer to it as a function, and the uppercase letter `β` is not recognized by BQN. In order to allow `π£` to be spelled as a 1-modifier `_π£` or 2-modifier `_π£_`, it is treated as an ordinary identifier character, so it must be separated from letters or numbers by spaces. +Because `π£` only ever refers to a 1-modifier or 2-modifer, it can never make sense to refer to it as a function, and the uppercase letter `β` is not recognized by BQN. To allow `π£` to be spelled as a 1-modifier `_π£` or 2-modifier `_π£_`, it's tokenized as an ordinary identifier character, so it has to be separated from adjacent letters or numbers with a space. ## Block headers @@ -122,7 +127,7 @@ Its syntax mirrors an application of the block. As suggested by the positioning, { F _op_ Β·βΏval: β¦ -In all cases special names still work just like in a headerless function. In this respect the effect of the header is the same as a series of assignments at the beginning of a function, such as the following translation of the second header above: +In all cases special names still work just like in a headerless function. In this respect, the effect of the header is the same as a series of assignments at the beginning of a function, such as the following translation of the second header above: { # Fn _apply β¨a,bβ©: Fn β π½ @@ -130,15 +135,17 @@ In all cases special names still work just like in a headerless function. In thi β¨a,bβ© β π© β¦ -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. +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, so 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 and operands allow [destructuring](expression.md#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. +Arguments and operands allow [destructuring](expression.md#destructuring) like assignment does. While assignment only tolerates lists of variables, header destructuring also allows constants. For the header to match, the argument must share the given structure, including the constants where they appear. Destruct β { π aβΏ1βΏβ¨b,Β·,2β©: aβb } Destruct 5βΏ1βΏβ¨7,Ο,2β© +It's also worth noting here that `[]` is a valid destructuring target, matching any length-0 array, even though it can't be used as a value since it's ambiguous. This syntax is also allowed in regular destructuring, but it's not very useful in that case. + ### Special names in headers Any element of a function or modifier header can be left nameless by using the corresponding special name in that position, instead of an identifier. For example, the header `π¨ π½_π£_πΎ π©:` incorporates as much vagueness as possible. It indicates a deferred 2-modifier, but provides no other information. @@ -154,21 +161,26 @@ A header can also be a plain name with no inputs, called a *label*. A label spec { _π£: # 1-Modifier { _π£_: # 2-Modifier -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, the name can't be used in code: it doesn't make sense to refer to a value while it is still being computed! +For immediate blocks, this is the only type of header possible, and it must use an identifier as there's no applicable special name. However, the name can't be used in code: it doesn't make sense to refer to a value while it's still being computed! ## Multiple bodies -Blocks 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 is that functions and deferred modifiers can have two headerless bodies (that is, no headers or predicatesβsee below): the first applies when there's one argument and the second when there are two. +Blocks can include more than one body, separated by semicolons `;`. The body used for a particular evaluation is chosen based on the inputs to the block. One special case is that functions and deferred modifiers can have two headerless bodies (that is, no headers or [predicates](#predicates)): the first applies when there's one argument and the second when there are two. Ambiv β { β¨1,π©β© ; β¨2,π¨,π©β© } + Ambiv 'a' + 'a' Ambiv 'b' Bodies with headers come before any that don't have them. When a block is called, its headers are checked in order for compatibility with the arguments, and the first body with a compatible header is used. CaseAdd β { 2π3:0βΏ5 ; 2ππ©:β¨1,2+π©β© ; ππ©:2βΏπ© } + 2 CaseAdd 3 + 2 CaseAdd 4 + CaseAdd 4 If no header is compatible, the call results in an error. @@ -190,25 +202,29 @@ These case-style headers function exactly the same as if they were preceded by ` ### Predicates -Destructuring with a header is quite limited, only allowing matching structure and data with exact equality. A predicate, written with `?`, allows you to test an arbitrary property before evaluating the rest of the body, and also serves as a limited kind of control flow. It can be thought of as an extension to a header, so that for example the following function requires the argument to have two elements and for the first to be less than the second before using the first body. Otherwise it moves to the next body, which is unconditional. +Destructuring with a header is limited, as it can only match a particular structure or value exactlyβnot, for example, a range of lengths. A predicate, written with `?`, allows you to test an arbitrary property before evaluating the rest of the body, and also serves as a limited kind of control flow. It can be thought of as an extension to a header. So the following function requires the argument to have two elements and for the first to be less than the second before using the first body. Otherwise it moves to the next body, which is unconditional. CheckPair β { πβ¨a,bβ©: a<b? "ok" ; "not ok" } CheckPair β¨3,8β© # Fails destructuring + CheckPair β¨1,4,5β© # Not a pair + CheckPair β¨3,Β―1β© # Not ascending -The body where the predicate appears doesn't need to start with a header, and there can be other statements before it. In fact, `?` functions just like a separator (like `β` or `,`) with a side effect. +The body where the predicate appears doesn't need to start with a header, and there can be other statements before it. Really, `?` works just like a separator (like `β` or `,`) with a side effect. { rββ½π© β 't'=βr ? r ; π© }Β¨ "test"βΏ"this" -So `r` is the reversed argument, and if its first character (the last one in `π©`) is `'t'` then it returns `r`, and otherwise we abandon that line of reasoning and return `π©`. This sounds a lot like an if statement. And `{ a<b ? a ; b }`, which computes `aβb` the hard way, shows how the syntax can be similar to a ternary operator. This is an immediate block with multiple bodies, something that makes sense with predicates but not headers. But `?;` offers more possibilities. It can support any number of options, with multiple tests for each oneβthe structure below is "if \_ and \_ then \_; else if \_ then \_; else \_". +So `r` is the reversed argument, and if its first character (the last one in `π©`) is `'t'` then it returns `r`, and otherwise we abandon that line of reasoning and return `π©`. + +This sounds a lot like an if statement. And `{ a<b ? a ; b }`, which computes `aβb` the hard way, shows how the syntax can be similar to a ternary operator. This is an immediate block with multiple bodies, something that makes sense with predicates but not headers. But `?;` offers more possibilities. It can support any number of options, with multiple tests for each oneβthe structure below is "if \_ and \_ then \_; else if \_ then \_; else \_". Thing β { π©β₯3? π©β€8? 2|π© ; π©=0? @ ; β } (β’ β ThingΒ¨) β10 # Table of arguments and results -This structure is still constrained by the rules of block bodies: each instance of `;` is a separate scope, so that variables defined before a `?` don't survive past the `;`. +This structure is still controlled by the rules of block bodies: each instance of `;` is a separate scope, so that variables defined before a `?` don't survive past the `;`. { 0=nββ π© ? β ; n } "abc" diff --git a/doc/choose.md b/doc/choose.md index 4362b9f5..7d53a122 100644 --- a/doc/choose.md +++ b/doc/choose.md @@ -2,17 +2,17 @@ # Choose -The 2-modifier Choose (`βΆ`) applies one function from a list `π`, based on a selection function `π½` that returns an index. It's a combinator form of [Pick](pick.md) (`β`), so that `{fβ(π¨π½π©)βπ β π¨Fπ©}` is a complete definition. For example, the function below subtracts 1 from an argument if negative and adds 1 if positive. +The 2-modifier Choose (`βΆ`) applies one function from a list `π`, based on the selecting index returned by a function `π½`. It's a combinator form of [Pick](pick.md) (`β`), so that `{fβ(π¨π½π©)βπ β π¨Fπ©}` is a complete definition. For example, the function below subtracts 1 from its argument if negative and adds 1 if positive. 0βΈβ€βΆβ¨-β1, +β1β©Β¨ 3βΏΒ―1βΏ5 -Here the selection function `π½` is `0βΈβ€`, while `π` is a list of two functions `β¨-β1, +β1β©`. On the first argument, `3`, `π½3` is `0β€3`, or `1`, so the function `+β1` from `π` is chosen. The use of array indices means "false" comes first in `π` and "true" comes second, which is backwards relative to if-else constructs in most programming languages (including BQN's own predicates). When using a comparison for `π½` I strongly prefer to phrase it as `nβΈ<` or `nβΈβ€` so that smaller values go through the first one and larger functions go through the second. This doesn't apply so much when comparing two arguments since one is smaller but the other's larger, so I don't have an easy technique for that. +Here the selection function `π½` is `0βΈβ€`, while `π` is a list of two functions `β¨-β1, +β1β©`. On the first argument, `3`, `π½3` is `0β€3`, or `1`, so the function `+β1` from `π` is chosen. The use of array indices means "false" comes first in `π` and "true" comes second, which is backwards relative to if-else constructs in most programming languages (including BQN's own [predicates](block.md#predicates)). When using a comparison for `π½` I strongly prefer to phrase it as `nβΈ<` or `nβΈβ€` so that smaller values go through the first one and larger functions go through the second. This doesn't apply so much when comparing two arguments since one is smaller but the other's larger, so I don't have an easy answer for that. 2 >βΆβ£βΏβ’ 6 # A minimum function (β) The advantage of using an index is that Choose works with any number of options. - Fn β (β"rtd"ββ)βΆβ¨β½, 1βΈβ, 1βΈβ, β’β© # Reverse, take 1, drop 1 + Fn β (β"rtd"ββ)βΆβ¨β½, 1βΈβ, 1βΈβ, β’β© # Reverse, take 1, drop 1 Fn "r123" @@ -20,7 +20,7 @@ The advantage of using an index is that Choose works with any number of options. Fn "123" # Default -The selection function in `Fn` uses [Index of](search.md#index-of) (`β`) to find the index of the first character in the list `"rtd"`. An extra value in `π` serves as a default function if it's none of those, since the result of `π½` is `3` in that case. A similar function that's often useful is [Bins](order.md#bins), for grouping inputs into intervals rather than by exact matching. +The selection function in `Fn` uses [Index of](search.md#index-of) (`β`) to find the index of the first character in the list `"rtd"`. An extra value in `π` serves as a default function if it's none of those, since the result of `π½` is `3` in that case. A similar function that's often useful is [Bins](order.md#bins), for grouping inputs into intervals rather than by exact matching. Choose is necessary for [tacit](tacit.md) programming, but tacit programming is not necessary to be an effective BQN programmer! Consider using block features like [predicates](block.md#predicates) when Choose isn't working with your program's flow. diff --git a/doc/compose.md b/doc/compose.md index 9c8c2106..84c3987c 100644 --- a/doc/compose.md +++ b/doc/compose.md @@ -6,7 +6,7 @@ DrawComp β"ββ" --> -Atop and Over are 2-modifiers that extend the idea of "apply this, then that" in two different ways. They're modelled after the mathematical notation fβg to compose two functions, and both do the same thing when there's one argument: `FβG x` or `FβG x` is `F G x`. +Atop and Over are 2-modifiers that extend the idea of "apply this, then that" in two different ways. They're modelled after the mathematical notation fβg to compose two functions, and both do the same thing when there's one argument: either `FβG x` or `FβG x` is `F G x`. | `Cmp` | `Cmp π©` | `π¨ Cmp π©` | Unified | On list |-------|---------|:--------------:|:-----------:|:-------: @@ -17,7 +17,7 @@ When there are two arguments, we might say Atop treats the right operand `πΎ` ## Atop -Of the two modifiers on this page, Atop is more common but less impactful. The composition `FβG` is equivalent to the 2-[train](train.md) `F G` (the trains page has hints on when you'd choose one or the other). Its definition `{Fπ¨Gπ©}` means that `G` is applied to one or two arguments and `F` is applied monadically to the result. It could be considered a "default way" to compose two functions. Keeps [tacit](tacit.md) programming syntax running smoothly, without making noise about it. Not like that busybody `βΈ`. Some examples: +Of the two modifiers on this page, Atop is more common but less impactful. The composition `FβG` is equivalent to the 2-[train](train.md) `F G` (the trains page has hints on when you'd choose one or the other). Its definition `{Fπ¨Gπ©}` means that `G` is applied to one or two arguments and `F` is applied monadically to the result. It's sort of a "default way" to compose two functions. Keeps [tacit](tacit.md) programming syntax running smoothly, without making noise about it. Not like that busybody `βΈ`. Some examples: `βββ ` is useful with one argument: `ββ l` is a list of indices for `l`. @@ -35,7 +35,7 @@ Once you get used to Over, it's painful to go without it. I'd use it all the tim Usually Over is used just for the dyadic meaning. If you have a composition that only works with one argument it's typical to write it with Atop (`β`). And cases that work with one or two arguments do come up from time to time, but they're fairly rare, so the examples below are just for two arguments. -A classic is the function `β‘ββ§`, which tests whether `π¨` is a reordering of `π©`. The idea is to sort both arrays with `β§` to remove the ordering information +A classic is the function `β‘ββ§`, which tests whether `π¨` is a reordering of `π©`. The idea is to sort both arrays with `β§` to remove the ordering information, then see if they match. "BQN" β‘ββ§ "QNB" "BQN" β‘ββ§ "BBQ" |
