From 7e5d0fcc39fd8a683fc7010af064849b454b432b Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Sat, 4 Jun 2022 17:40:31 -0400 Subject: Further editing --- doc/glossary.md | 2 +- doc/lexical.md | 12 ++++++------ doc/logic.md | 40 +++++++++++++++++++++------------------- doc/map.md | 14 +++++++------- doc/match.md | 12 ++++++------ doc/namespace.md | 26 +++++++++++++------------- doc/transpose.md | 2 +- 7 files changed, 55 insertions(+), 53 deletions(-) (limited to 'doc') diff --git a/doc/glossary.md b/doc/glossary.md index 7f8d4bf4..14968154 100644 --- a/doc/glossary.md +++ b/doc/glossary.md @@ -96,7 +96,7 @@ The possible roles are: * **Access**: To get the current value of a field from a namespace. * [**Export**](namespace.md#exports): Declare a variable to be accessible from the outside, that is, make it a field. * [**Object**](oop.md): Informal term for a namespace that holds mutable state. -* **Alias**: A different "outside" name chosen for a field in a destructuring assignment. +* [**Alias**](namespace.md#imports): A different "outside" name chosen for a field in a destructuring assignment. ## Tokens diff --git a/doc/lexical.md b/doc/lexical.md index 3183f7f3..b6ca828d 100644 --- a/doc/lexical.md +++ b/doc/lexical.md @@ -4,7 +4,7 @@ BQN uses lexical scope, like most modern functional programming languages including Javascript, Scheme, and Julia, and like Dyalog APL's dfns (tradfns are dynamically scoped). This document describes how lexical scoping works, and a few small details relevant to BQN's version of it. -In short, every [block](block.md) is a separate scope that can refer to identifiers in containing scopes. When evaluated, the block makes a variable for each identifier defined in it (including arguments and operands). The blocks that it contains will now access these variables. In the first level of a block, variables must be defined before they can be used, but in child blocks, a variable can be used regardless of where it's defined, as long as the definition is evaluated before the child block is. +In short, every [block](block.md) is a separate scope, but can use identifiers in containing scopes. Each time it's evaluated, the block makes a variable for each identifier defined in it (including arguments and operands). The blocks that it contains might access these variables. At the top level of a block, identifiers must be defined before they can be used, but in child blocks, an identifier can be used even if it's defined later, as long as that use isn't evaluated before the definition can set the variable value. ## Scopes @@ -18,7 +18,7 @@ Scoping is a mechanism that allows the same variable name to refer to different Above, the scope of the first `a` is the entire program, while the scope of the second `a` is limited to the body of `F`. So one form of context is that a name might mean different things depending on which block contains it. But even the exact same instance of a name in the source code might mean multiple things! A second kind of context is which evaluation of a block uses the name. -Without this ability BQN would be pretty limited: for example, an [object](oop.md)'s fields are variables. If the variable value didn't depend on what object contained it, there could effectively only be one instance of each object! While it's needed all the time, the most direct way to demonstrate one name meaning multiple things is with recursion. The (fragile) function below labels each element in a nested list structure with its index in the list containing it. +Without this ability BQN would be pretty limited: for example, an [object](oop.md)'s fields are variables. If the variable value didn't depend on what object contained it, there could effectively only be one instance of each object! It's not the most common use case, but recursion is the most direct way to demonstrate a name meaning multiple things at once. The (fragile) function below labels each element in a nested list structure with its index in the list containing it. Label ← { i←↕≠𝕩 ⋄ i ≍ 𝕊⍟=¨ 𝕩 } @@ -30,7 +30,7 @@ These examples probably work like you expect—they're meant to highlight the fe ## Visibility -A scope can view and modify (with `↩`) variables in other scopes that contain it. We say these variables are visible in the inner scopes. Variables at the top level of a program are visible to all the code in that program, so that we might call them "global". That would be a little misleading though, because for example each file is an entire program, so if one file is imported from another then it can't read the first file's variables. +A scope can view and [modify](expression.md#assignment) (with `↩`) variables in other scopes that contain it. We say these variables are *visible* in the inner scopes. Variables at the top level of a program are visible to all the code in that program, so that we might call them "global". That's somewhat misleading, because for example each file is an entire program, so if one file is imported from another then it can't read the first file's variables. counter ← 0 inc ← 6 @@ -96,7 +96,7 @@ Each result keeps its own counter and the different copies don't interfere with Each counter function has access to the environment containing its `counter` and `inc`, even though the block that created that environment (`_makeCount`) has finished execution—it must have finished, since we are now using the function it returns on the last line. There's nothing particularly weird about this; just because a block creates an environment when it starts doesn't mean it has to destroy it when it finishes. From the mathematical perspective, it's easiest to say the environment exists forever, but a practical implementation will perform garbage collection to free environments that are no longer reachable. -Since a function like `C1_4` maintains access to all the variables it needs to run, we say it *encloses* those variables, and call it a *closure*. It doesn't need to modify them. For example, even the following definition of `stdDev` is a closure. +Since a function like `C1_4` maintains access to all the variables it needs to run, we say it *encloses* those variables, and call it a *closure*. It doesn't need to modify them. For example, the following definition of the theoretical standard deviation function `StdDev` is also a closure. stdDev ← { # Arithmetic mean @@ -124,7 +124,7 @@ How does an environment know which of the many environments corresponding to the ## Mutation -The value of a variable can be modified with `↩`. It's similar to definition `←` in that it sets the value of the variable, but the way it interacts with scoping is completely different. Defining creates a new variable in the current scope, and modifying refers to an existing variable in the current scope or a parent. In scoping terms, modifying is more like an ordinary variable reference than a definition. +The value of a variable can be modified with `↩`. It's similar to definition `←` in that it sets the value of the variable, but the way it interacts with scoping is completely different. Definition creates a new variable in the current scope, and modification refers to an existing variable in the current scope or a parent. In scoping terms, a modification is more like an ordinary variable reference than a definition. When a variable's modified, functions with access to it see the new value. They have access to the variable, not any particular value that it has. @@ -135,7 +135,7 @@ When a variable's modified, functions with access to it see the new value. They factor ↩ 5 Mul 6 # A new result -Only code with access to a variable can modify it! This means that if none of the code in a variable's scope modifies it, then the variable is a constant in each environment that contains it (not necessarily across environments). That is, constant once it's defined: it's still possible to get an error if the variable is accessed before being defined. +Only code with access to a variable can modify it! This means that if none of the code in a variable's scope modifies it, then the variable is a constant in each environment that contains it (not necessarily across environments). That is, constant once it's defined: remember that it's still possible to get an error if the variable is accessed before being defined. { { a } ⋄ a←4 } diff --git a/doc/logic.md b/doc/logic.md index 080e54f1..89312e92 100644 --- a/doc/logic.md +++ b/doc/logic.md @@ -2,26 +2,12 @@ # Logic functions: And, Or, Not (also Span) -BQN uses the mathematical symbols `∧` and `∨` for logical *and* and *or*, and `¬` for *not* (APL's `~` is discarded since it looks like `˜`, and is less common in mathematics today). These functions are arithmetically extended to apply to all numbers. In the case of Not, that means the linear function `1⊸-`. The two-argument functions have bilinear extensions: And is identical to Times (`×`), while Or is `×⌾¬`, following De Morgan's laws (other ways of obtaining a function for Or give an equivalent result—there is only one bilinear extension). +BQN uses the mathematical symbols `∧` and `∨` for logical *and* and *or*, and `¬` for *not* (APL's `~` is discarded since it looks like `˜`, and is less common in mathematics today). That is, on two booleans `∧` is 1 if both are 1, and `∨` is if either is 1. `¬` flips its argument, returning 1 if the argument is 0 and 0 if it's 1. The logic functions are also considered [arithmetic](arithmetic.md) and thus are [pervasive](arithmetic.md#pervasion). -If the arguments are probabilities of independent events, then an extended function gives the probability of the boolean function on their outcomes (for example, if *A* occurs with probability `a` and *B* with probability `b` independent of *A*, then *A* or *B* occurs with probability `a∨b`). These extensions have also been used in complexity theory, because they allow mathematicians to transfer a logical circuit from the discrete to the continuous domain in order to use calculus on it. +These boolean functions are arithmetically extended to apply to all numbers. Not returns `1-𝕩`, And returns `𝕨×𝕩`, and Or does a more complicated computation `𝕨×⌾¬𝕩`. Both valences of `¬` are equivalent to the fork `1+-`. The dyadic valence, called "Span", computes the number of integers in the range from `𝕩` to `𝕨`, inclusive, when both arguments are integers and `𝕩≤𝕨` (note the reversed order, which is used for consistency with subtraction). This function has many uses, and in particular is relevant to the [Windows](windows.md) function. -These functions are considered [arithmetic](arithmetic.md) functions and thus are [pervasive](arithmetic.md#pervasion). - -## Definitions - -We define: - - Not ← 1+- # also Span - And ← × - Or ← ×⌾¬ - -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 ← +-× - ## Examples We can form truth [tables](map.md#table) including the non-integer value one-half: @@ -34,11 +20,21 @@ We can form truth [tables](map.md#table) including the non-integer value one-hal As with logical And and Or, any value and 0 is 0, while any value or 1 is 1. The other boolean values give the identity values for the two functions: 1 and any value gives that value, as does 0 or the value. -## Why not GCD and LCM? +## Definitions -APL provides [GCD](https://aplwiki.com/wiki/GCD) and [LCM](https://aplwiki.com/wiki/LCM) as extensions of And and Or, while BQN doesn't make these functions primitives. The main reason for omitting them functions is that they are complicated and, when applied to real or complex numbers, require a significant number of design decisions where there is no obvious choice (for example, whether to use comparison tolerance). On the other hand, these functions are fairly easy to implement, which allows the programmer to control the details, and also add functionality such as the extended GCD. Possible implementations for GCD and LCM are shown in [bqncrate](https://mlochbaum.github.io/bqncrate) ([GCD](https://mlochbaum.github.io/bqncrate/?q=gcd), [LCM](https://mlochbaum.github.io/bqncrate/?q=lcm)). +We define -A secondary reason is that the GCD falls short as an extension of Or, because its identity value 0 is not total. `0∨x`, for a real number `x`, is actually equal to `|x` and not `x`: for example, `0∨¯2` is `2` in APL. This means the identity `0∨x ←→ x` isn't reliable in APL. + Not ← 1+- # also Span + And ← × + Or ← ×⌾¬ + +using a [train](train.md) for Not and [Under](under.md) for Or. The latter expands to `Or ← ¬∘×○¬`, since Not is a self-inverse `¬⁼ ←→ ¬`: 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 ← +-× + +The logic functions are extended to all numbers by making them linear in every argument. In the case of Not, that means the linear function `1⊸-`. The two-argument functions have bilinear extensions: And is identical to Times (`×`), while Or is `×⌾¬`, following De Morgan's laws (other ways of obtaining a function for Or give an equivalent result—there is only one bilinear extension). + +If the arguments are probabilities of independent events, then an extended function gives the probability of the boolean function on their outcomes. For example, if *A* occurs with probability `a` and *B* with probability `b` independent of *A*, then at least one of *A* or *B* occurs with probability `a∨b`. These extensions have also been used in complexity theory, because they allow mathematicians to transfer a logical circuit from the discrete to the continuous domain in order to use calculus on it. ## Identity values @@ -47,3 +43,9 @@ It's common to apply a [fold](fold.md) `∧´` or `∨´` to a list (checking wh It's not hard to prove that the bilinear extensions have the identity values we want. Of course `1∧x` is `1×x`, or `x`, and `0∨x` is `0×⌾¬x`, or `¬1׬x`, giving `¬¬x` or `x` again. Both functions are commutative, so these values are identities on the right as well. Other logical identities do not necessarily hold. For example, in boolean logic And distributes over Or and vice-versa: `a∧b∨c ←→ (a∧b)∨(a∧c)`. But substituting `×` for `∧` and `+-×` for `∨` we find that the left hand side is `(a×b)+(a×c)+(a×b×c)` while the right gives `(a×b)+(a×c)+(a×b×a×c)`. These are equivalent for arbitrary `b` and `c` only if `a=a×a`, that is, `a` is 0 or 1. In terms of probabilities the difference when `a` is not boolean is caused by failure of independence. On the left hand side, the two arguments of every logical function are independent. On the right hand side, each pair of arguments to `∧` are independent, but the two arguments to `∨`, `a∧b` and `a∧c`, are not. The relationship between these arguments means that logical equivalences no longer apply. + +## Why not GCD and LCM? + +APL provides [GCD](https://aplwiki.com/wiki/GCD) and [LCM](https://aplwiki.com/wiki/LCM) as extensions of And and Or, while BQN doesn't make these functions primitives. The main reason for omitting them functions is that they are complicated and, when applied to real or complex numbers, require a significant number of design decisions where there's no obvious choice (for example, whether to use comparison tolerance). On the other hand, these functions are fairly easy to implement, which allows the programmer to control the details, and also add functionality such as the extended GCD. Possible implementations for GCD and LCM are shown in [bqncrate](https://mlochbaum.github.io/bqncrate) ([GCD](https://mlochbaum.github.io/bqncrate/?q=gcd), [LCM](https://mlochbaum.github.io/bqncrate/?q=lcm)). + +A secondary reason is that the GCD falls short as an extension of Or, because its identity value 0 is not total. `0∨x`, for a real number `x`, is actually equal to `|x` and not `x`: for example, `0∨¯2` is `2` in APL. This means the identity `0∨x ←→ x` isn't reliable in APL. diff --git a/doc/map.md b/doc/map.md index c8a4d65d..bbde53d0 100644 --- a/doc/map.md +++ b/doc/map.md @@ -4,7 +4,7 @@ Mapping a function over an array means to call it on each element of that array, creating an array of results. It's also possible to map over two arrays, applying the function to various choices of one element from each, but there's no longer a single correct way to iterate over these elements. -BQN has two 1-modifiers to map over arrays: Each (`¨`) and Table (`⌜`). On two arguments, Table applies its operand to all combinations of elements while Each creates a one-to-one or one-to-many matching. Since they apply to elements, these modifiers are different from Cells (`˘`) or its generalization Rank (`⎉`), which apply the function to array cells. The modifier [Depth](depth.md#the-depth-modifier) (`⚇`) is a generalization of Each, so that `¨` is `⚇¯1`; however, it can't be used to implement Table without some additional array operations. +As a result, BQN has two 1-modifiers to map over arrays: Each (`¨`) and Table (`⌜`). On two arguments, Table applies its operand to all combinations of elements while Each creates a one-to-one or one-to-many matching. Since they apply to elements, these modifiers are different from [Cells](rank.md#cells) (`˘`) or its generalization [Rank](rank.md#rank) (`⎉`), which apply the function to array cells. The modifier [Depth](depth.md#the-depth-modifier) (`⚇`) is a generalization of Each, so that `¨` is `⚇¯1`; however, it can't be used to implement Table without some additional array operations. ## One-argument mapping @@ -105,7 +105,7 @@ The Table modifier applies its operand function to every possible combination of "ABC" ≍⌜ "01234" -Its name comes from the "multiplication table" or "times table" often used to teach arithmetic, and with it you can easily make such a table, by repeating the same argument with Self (`˜`): +Its name comes from the "multiplication table" or "times table" often used to teach arithmetic, and with it you can easily make such a table, by repeating the same argument with [Self](swap.md) (`˜`): ×⌜˜ 1+↕6 @@ -144,11 +144,11 @@ Given two arguments of matching shapes, Each performs what's sometimes called a "ABCD" ≍¨ "0123" -This makes for a lot fewer applications than Table. Only the diagonal elements from Table's result are seen, as we can check with [Transpose](transpose.md). +This makes for a lot fewer applications than Table. Only the diagonal elements from Table's result are seen, as we can check with [Reorder Axes](transpose.md#reorder-axes). 0‿0 ⍉ "ABCD" ≍⌜ "0123" -If the argument lengths don't match then Each gives an error. This contrasts with zip in many languages, which drops elements from the longer argument (this is natural for linked lists). This flexibility is rarely wanted in BQN, and having an error right away saves debugging time. +If the argument lengths don't match then Each gives an error. This differs from zip in many languages, which drops elements from the longer argument (this is natural for linked lists). This flexibility is rarely wanted in BQN, and having an error right away saves debugging time. "ABC" ≍¨ "01234" @@ -156,10 +156,10 @@ Arguments can have any shape as long as the axis lengths match up. As with Table (>⟨20‿30‿10,50‿40‿60⟩) +⟜↕¨ 2‿1‿0≍3‿2‿1 -But arguments don't have to have exactly the same shape: just the same length along corresponding axes. These axes are matched up according to the [leading axis convention](leading.md), so that one argument's shape has to be a prefix of the other's. With equal ranks, the shapes do have to match as we've seen above. +But arguments don't have to have exactly the same shape: just the same length along corresponding axes. These axes are matched up by [leading axis agreement](leading.md#leading-axis-agreement), so that one argument's shape has to be a prefix of the other's. With equal ranks, the shapes do have to match as we've seen above. ≢ (0‿2‿6⥊@) ≍¨ 0‿1⥊0 # Too small - ≢ (0‿2‿6⥊@) ≍¨ 0‿2⥊0 # Just right + ≢ (0‿2‿6⥊@) ≍¨ 0‿3⥊0 # Too large -Leading axis agreement is described further [here](leading.md#leading-axis-agreement). + ≢ (0‿2‿6⥊@) ≍¨ 0‿2⥊0 # Just right diff --git a/doc/match.md b/doc/match.md index 9d102238..dddcd50d 100644 --- a/doc/match.md +++ b/doc/match.md @@ -17,11 +17,11 @@ Match always gives the same result as [Equals](arithmetic.md#comparisons) (`=`) Match compares arrays based on their fundamental properties—[shape](shape.md) and elements—and not the [fill element](fill.md), which is an inferred property. Since it can be computed differently in different implementations, using the fill element in Match could lead to some confusing results. Even if the implementation doesn't define a fill for `'a'‿'b'‿'c'`, it should still be considered to match `"abc"`. -To give a precise definition, two arrays are considered to match if they have the same shape and all corresponding elements from the two arrays match. Every array has a finite [depth](depth.md) so this recursive definition always ends up comparing non-arrays, or atoms. An array never matches an atom, so the result if only one argument is an atom is `0`. The interesting case is when both arguments are atoms, discussed below. +To give a precise definition, two arrays are considered to match if they have the same shape and all corresponding elements from the two arrays match. Every array has a finite [depth](depth.md), so this recursive definition always ends up comparing non-arrays, or atoms. And because an array never matches an atom, the result if only one argument is an atom is `0`. The interesting case is when both arguments are atoms, discussed below. ## Atomic equality -Atoms in BQN have six possible [types](types.md): number, character, function, 1-modifier, 2-modifier, and namespace. Equality is not allowed to fail for any two arguments, so it needs to be defined on all of these types. +Atoms in BQN have six possible [types](types.md): number, character, function, 1-modifier, 2-modifier, and namespace. Equality testing isn't allowed to fail for any two arguments, so it needs to be defined on all of these types. Starting with the easiest rules, values with different types are never equal to each other. @@ -33,12 +33,12 @@ Two characters are equal when they have the same code point. Numeric equality de 1.25 = 1 + 0.25 -Mutable types are more difficult. Here there are three cases: +Operations and namespaces are more difficult. Here there are three cases: - Primitives are equal if they have the same glyph. - Compound functions or modifiers are split into components. - Block instances or namespaces are equal if they are the same instance. -The first two are fairly similar to how numbers and arrays work. Primitives and compounds like trains, or modifiers with bound operands, are immutable, so they are defined purely by what components they contain. +The first two are fairly similar to how numbers and arrays work. Primitives and compounds like trains, or modifiers with bound operands, are immutable, so they're defined purely by what components they contain. ⟨+,-,×⟩ = ⟨+,-,÷⟩ @@ -58,7 +58,7 @@ The final point above about block instances is subtler. An instance of a block f G 8 F 5 # Another result—the definition of insanity! -(A side note is that BQN restricts what can cause these side effects: they can only happen by calling a block function or modifier, and never a primitive or purely tacit operation). Now suppose we share the value of `F` with another variable. When we apply `G`, the result of `F` might change, but so does `F1`! This effect is called [aliasing](https://en.wikipedia.org/wiki/Aliasing_(computing)). +(A side note is that BQN restricts what can cause these side effects: they can only happen by calling a block function or modifier, and never a primitive or purely [tacit](tacit.md) operation). Now suppose we share the value of `F` with another variable like `F1` below. When we apply `G`, the result of `F` might change, but so does `F1`! This effect is called [aliasing](https://en.wikipedia.org/wiki/Aliasing_(computing)). F1 ← F {𝕏 6}¨ F‿F1 @@ -66,7 +66,7 @@ The final point above about block instances is subtler. An instance of a block f G 3 {𝕏 6}¨ F‿F1 -In some cases you might not be able to demonstrate aliasing so cleanly. A function such as a random number generator changes its own state, so calling one function will change the other. Comparison tells you whether two blocks are the same. +In some cases you might not be able to demonstrate aliasing so cleanly. A function such as a random number generator changes its own state, so calling one function will change the other. But comparison tells you directly whether two blocks are the same. f = f1 diff --git a/doc/namespace.md b/doc/namespace.md index 5b7f67e5..896b2ffb 100644 --- a/doc/namespace.md +++ b/doc/namespace.md @@ -2,7 +2,7 @@ # Namespaces -A namespace is a type of value that groups together several values (fields) from the same scope. A block or file returns a namespace if it contains any export arrows `⇐` at the top level, and fields from namespaces can be accessed with either dot syntax or destructuring assignment. A namespace can be mutable only if any of the code in it uses `↩` to change the value of a field. +A namespace is a type of value that groups together several variables (fields) from the same scope. A block or file returns a namespace if it contains any export arrows `⇐` at the top level, and fields from namespaces can be accessed with either dot syntax or destructuring assignment. A namespace can be mutable only if any of its source code uses `↩` to change the value of a field. The following quick example shows a few ways to use a namespace returned by `•Import`: @@ -19,11 +19,11 @@ An here's how the contents of file.bqn might look in order to define the variabl ## Uses -The features of namespaces that make them useful in BQN programming are encapsulation and mutability. But these are exactly the same features that [closures](lexical.md#closures) provide! In fact a namespace is not much more than a closure with a name lookup system. Consequently namespaces don't really expand the basic functionality of the language, but just make it easier to use. +The features of namespaces that make them useful in BQN programming are encapsulation and mutability. But these are exactly the same features that [closures](lexical.md#closures) provide! In fact a namespace is not much more than a closure with a name lookup system. Consequently namespaces don't expand the basic functionality of the language, but just make it easier to use. Namespaces improve encapsulation by allowing many values to be exported at once. With only one way to call them, functions and modifiers aren't such a good way to define a large part of a program. With a namespace you can define lots of things and expose exactly the ones you want to the rest of the world. For example, it's typical for files to define namespaces. A reader can see the exported values just by searching for `⇐`, and if you're nice, you might declare them all at the beginning of the file. Careful use of exports can guarantee that potentially dangerous functions are used correctly: if it's only valid to call function `B` after function `A` has been called, export `AB⇐{A𝕩⋄B𝕩}` and don't export `B`. -Mutability means that the behavior of one namespace can change over the course of the program. Mutability is often a liability, so make sure you really need it before leaning too heavily on this property. While there's no way to tell from the outside that a particular namespace is mutable, you can tell it isn't if the source code doesn't contain `↩`, as this is the only way it can modify the variables it contains. +Mutability means that the behavior of one namespace can change over the course of the program. Mutability is often a liability, so make sure you really need it before leaning too heavily on this property. While there's no way to tell from the outside that a particular namespace is mutable, you can tell it isn't if the source code doesn't contain `↩`, as this is the only way it could modify the variables it contains. A namespace that makes use of mutability is essentially an object: a collection of state along with operations that act on it. [Object-oriented programming](oop.md) is the other major use of namespaces. Contrary to the name, there's never a need to orient your programming around objects, and it's perfectly fine to use an object here or there when you need to, for instance to build a mutable queue of values. @@ -40,22 +40,22 @@ The double arrow `⇐` is used to export variables from a block or file, making ## Imports -There are also two ways to get values out of a namespace, such as `example` defined above. First, it might be used in a [destructuring](expression.md#destructuring) assignment like the one below. This assignment's target looks like a list, where each entry specifies one of the names exported by the block and what it should be assigned to. The element can be either a single name, like `b`, which gives both, or an aliasing expression like `b2⇐b`. In this case, the value `b` from the namespace is used, but it's given the name `b2` instead of `b`. Imported names can be repeated—but the variables defined can't—and all the names can be spelled with any role (the role is ignored). +There are also two ways to get values out of a namespace, such as `example` defined above. First, one field at a time can be retrieved with dot syntax: write the namespace, then a dot `.`, then another name. - ⟨alias⇐a, b, c0‿c1⇐c, b2⇐b⟩ ← example + example.b -If aliasing with `⇐` is never used (or each use is parenthesized), the names can be given as a strand with `‿`. + {n⇐7}.n - c‿a ← example +The part on the left can be anything with a subject role, although it will often need to be parenthesized because `.` has higher precedence than any operator. This allows it to be chained like `a.b.c` if a namespace has a value that is also a namespace (and so on). -The arrows `⇐` used for importing don't indicate that the surrounding block is a namespace or export variables. However, a single statement can both import and export, if it's a destructuring assignment and the main assignment arrow is `⇐`. +Second, a namespace might be used in a [destructuring](expression.md#destructuring) assignment like the one below. This assignment's target looks like a list, where each entry specifies one of the names exported by the block and what it should be assigned to. The element can be either a single name, like `b`, which gives both, or an aliasing expression like `b2⇐b`. In this case, the value `b` from the namespace is used, but it's given the name `b2` instead of `b`. Imported names can be repeated—but the variable names defined can't be—and all the names can be spelled with any role (the role is ignored). - ⟨two, vars⟩ ⇐ •Import "stuff.bqn" + ⟨alias⇐a, b, c0‿c1⇐c, b2⇐b⟩ ← example -The second way to get a value (just one at a time) from a namespace is dot syntax: write the namespace, then a dot `.`, then another name. +If aliasing with `⇐` is never used (or each use is parenthesized), the names can be given as a strand with `‿`. - example.b + c‿a ← example - {n⇐7}.n +The arrow `⇐` used for an alias doesn't export anything or indicate that its block is a namespace. However, a single statement can both import and export, if it's a destructuring assignment and the main assignment arrow is `⇐`. -The syntax is any subject followed by a dot and then a name. This can be chained like `a.b.c` if a namespace has a value that is also a namespace (and so on). + ⟨two, vars⟩ ⇐ •Import "stuff.bqn" diff --git a/doc/transpose.md b/doc/transpose.md index f1f978ad..e11ab359 100644 --- a/doc/transpose.md +++ b/doc/transpose.md @@ -18,7 +18,7 @@ Transpose is named this way because it exchanges the two axes of the matrix. Abo With two axes the only interesting operation of this sort is to swap them (and with one or zero axes there's nothing interesting to do, and `⍉` just returns the argument array). But a BQN programmer may well want to work with higher-rank arrays—although such a programmer might call them "tensors"—and this means there are many more ways to rearrange the axes. Transpose extends to high-rank arrays to allow some useful special cases as well as completely general axis rearrangement, as described below. -## Monadic Transpose +## Transposing tensors APL extends matrix transposition to any rank by reversing all axes for its monadic `⍉`, but this generalization isn't very natural and is almost never used. The main reason for it is to maintain the equivalence `a MP b ←→ b MP⌾⍉ a`, where `MP ← +˝∘×⎉1‿∞` is the generalized matrix product. But even here APL's Transpose is suspect. It does much more work than it needs to, as we'll see. -- cgit v1.2.3