Skip to content

Commit

Permalink
Further editing
Browse files Browse the repository at this point in the history
  • Loading branch information
mlochbaum committed Jun 4, 2022
1 parent cd0f461 commit 7e5d0fc
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 102 deletions.
2 changes: 1 addition & 1 deletion doc/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions doc/lexical.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 ≍ 𝕊⍟=¨ 𝕩 }

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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 }

Expand Down
40 changes: 21 additions & 19 deletions doc/logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -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.
14 changes: 7 additions & 7 deletions doc/map.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -144,22 +144,22 @@ 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"

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

(>⟨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
Loading

0 comments on commit 7e5d0fc

Please sign in to comment.