From 1ad421580ea5fa0c447bba5a37f8571927e9e942 Mon Sep 17 00:00:00 2001 From: Jeremy Howard Date: Thu, 12 Nov 2026 11:46:56 +1000 Subject: [PATCH] =?UTF-8?q?Apply=20operators=20to=20whole=20trains=20with?= =?UTF-8?q?=20=E2=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nbs/autodiff.qmd | 12 ++--- nbs/comparison.qmd | 2 +- nbs/composition.qmd | 38 ++++++++++----- nbs/data.ipynb | 2 +- nbs/evaluation.qmd | 2 + nbs/glyphs/at.qmd | 4 +- nbs/glyphs/circle-star.qmd | 4 +- nbs/glyphs/comma.qmd | 2 +- nbs/glyphs/dot.qmd | 8 ++-- nbs/glyphs/jot-diaeresis.qmd | 6 +-- nbs/glyphs/jot.qmd | 10 ++++ nbs/glyphs/left-tail-arrow.qmd | 4 +- nbs/glyphs/minus.qmd | 2 +- nbs/glyphs/obelus.qmd | 2 +- nbs/glyphs/partial.qmd | 4 +- nbs/glyphs/pi.qmd | 2 +- nbs/glyphs/quad-equal.qmd | 8 ++-- nbs/glyphs/radical.qmd | 2 +- nbs/glyphs/right-tail-arrow.qmd | 2 +- nbs/glyphs/star.qmd | 2 +- nbs/glyphs/superscript-minus.qmd | 4 +- nbs/glyphs/tricolon.qmd | 2 +- nbs/index-notation.qmd | 2 +- nbs/indexing.ipynb | 6 +-- nbs/llms.txt | 2 +- nbs/mathish.qmd | 2 +- nbs/python.ipynb | 67 ++++++++++++++++++++++---- nbs/scripts.ipynb | 2 +- nbs/study/01_basics.ipynb | 6 +-- nbs/study/03_operators.ipynb | 20 ++++---- nbs/study/05_searching-indexing.ipynb | 4 +- nbs/study/06_rearranging-arrays.ipynb | 10 ++-- nbs/style.qmd | 4 +- nbs/syntax.qmd | 8 ++-- nbs/system/replace.qmd | 2 +- python/basedpl/syntax.py | 2 +- src/eval/binder.rs | 45 +++++++++++++++--- tests/core.rs | 19 ++++++++ tests/reference/aplcart.bpl | 68 ++++++++++++++------------- tests/reference/april_mathish.bpl | 2 +- tests/reference/bqn.bpl | 16 +++---- tests/reference/core.bpl | 12 ++--- tests/reference/regex.bpl | 2 +- tests/reference/static.bpl | 2 +- tests/test_expressions.py | 3 ++ 45 files changed, 283 insertions(+), 147 deletions(-) diff --git a/nbs/autodiff.qmd b/nbs/autodiff.qmd index c2b03784..4b1040d1 100644 --- a/nbs/autodiff.qmd +++ b/nbs/autodiff.qmd @@ -4,7 +4,7 @@ ```bpl ⊼∂3 ⍝ 6 -(+/⊼)∂ 1 2 3 ⍝ 2 4 6 ++/⊼ ∂ 1 2 3 ⍝ 2 4 6 ``` The [`∂` page](glyphs/partial.qmd) lists what `∂` differentiates and the errors it gives. @@ -96,10 +96,10 @@ ste←{⌊ω} ◇ ste∂←{α} {+/steω×2}∂ 1.5 2.5 ⍝ 2 2 ``` -`•detach X` gives `X` with every derivative removed. `∂` then treats it as a constant, as JAX treats the result of `stop_gradient`. `⊢וdetach` multiplies its argument by a detached copy of itself. Its derivative at 3 is the copy's value, 3, where `⊼∂3` gives 6: +`•detach X` gives `X` with every derivative removed. `∂` then treats it as a constant, as JAX treats the result of `stop_gradient`. `×↢•detach` multiplies its argument by a detached copy of itself. Its derivative at 3 is the copy's value, 3, where `⊼∂3` gives 6: ```bpl -(⊢וdetach)∂3 ⍝ 3 +×↢•detach ∂3 ⍝ 3 ``` A value that `f` writes to a name outside itself loses its derivatives when `∂` returns. A function gives the same values with and without `∂`. @@ -144,11 +144,11 @@ sq←{+/⊼ω} ◇ g←sq∂ 1 2 3 The second call replays the program that the first call built. Four numbers need a program of their own. -A dfn keeps its programs with its definition. Any other function, such as a train, keeps them by its structure for the rest of the session. The structure leaves out the arrays that the function binds, which become inputs, as a dfn's free names do. So `+/w×⊢` replays its program when `w` holds new values of the same type and shape: +A dfn keeps its programs with its definition. Any other function, such as a train, keeps them by its structure for the rest of the session. The structure leaves out the arrays that the function binds, which become inputs, as a dfn's free names do. So `+/w×` replays its program when `w` holds new values of the same type and shape: ```bpl -w←1 2 3 ◇ g←(+/w×⊢)∂ 4 5 6 -w←7 8 9 ◇ (•explain "(+/w×⊢)∂ 4 5 6").programs.event ⍝ ["replayed"] +w←1 2 3 ◇ g←+/w× ∂ 4 5 6 +w←7 8 9 ◇ (•explain "+/w× ∂ 4 5 6").programs.event ⍝ ["replayed"] ``` ### What stops a trace diff --git a/nbs/comparison.qmd b/nbs/comparison.qmd index 63061962..9b073ece 100644 --- a/nbs/comparison.qmd +++ b/nbs/comparison.qmd @@ -469,7 +469,7 @@ a +˝∘×⎉1‿∞ b ```bpl a←2 3ρι6ₓ ◇ b←3 2ρι6ₓ -a (+⌿×)⍤1 ∞ b ⍝ [10 13◇28 40]ₓ +a +⌿×∘⍤1 ∞ b ⍝ [10 13◇28 40]ₓ ``` ### A vector bound into a composition diff --git a/nbs/composition.qmd b/nbs/composition.qmd index 49f8e574..c7dbc49b 100644 --- a/nbs/composition.qmd +++ b/nbs/composition.qmd @@ -26,6 +26,16 @@ v←2 4 9 ◇ +/÷≢ v ⍝ 5 v←2 4 9 ◇ +/÷≢v ⍝ 0ₓ ``` +## Operators on trains + +Before an operator, `∘` finishes the current run as that operator's left operand. `⊂⊢∘⌸` is `(⊂⊢)⌸`, and `+/÷≢∘⍤1` is `(+/÷≢)⍤1`. The right operand of a dyadic operator follows as usual: + +```bpl +m←2 3ρι6 ◇ +/÷≢∘⍤1m ⍝ 1 4 +``` + +The boundary includes the whole run on its left. Keep a left argument or an earlier function outside that run with spaces or parentheses. Named operators work the same way as glyphs. Before a function, `∘` remains [Atop](#atop-and-over). + ## Identity functions In a train, `⊢` stands for the right argument and `⊣` for the left. A fork with `⊢` reads well where the argument appears once. `⊢÷+/` divides each item by the sum: @@ -45,12 +55,14 @@ n←⊽⁻+÷ ◇ n 1.5 ⍝ 1.416666667 ## Atop and Over -[Atop](glyphs/jot.qmd), `f∘g`, applies `g` and then `f`. `X f∘g Y` is `f(X g Y)`. `⌊∘÷` divides, then rounds down: +The two-function train `⌊÷` divides, then rounds down: ```bpl -7 ⌊∘÷ 2 ⍝ 3ₓ +7 ⌊÷ 2 ⍝ 3ₓ ``` +[Atop](glyphs/jot.qmd), `∘`, writes this composition explicitly. `f∘g` applies `g` and then `f`, as the train `f g` does. `X f∘g Y` is `f(X g Y)`. + [Over](glyphs/circle-diaeresis.qmd), `f⍥g`, applies `g` to each argument before `f`. `X f⍥g Y` is `(g X)f(g Y)`. `=⍥≢` tests whether two lists have the same length: ```bpl @@ -95,20 +107,20 @@ x←3 ◇ x² ⍝ 9 fahrenheit←32+1.8× ◇ fahrenheit⁻ 212 ⍝ 100 ``` -A superscript straight after a number applies to the number. To invert a function that ends in a number, put the function in parentheses: +A superscript straight after a number applies to the number. To invert a function that ends in a number, finish the function before `⁻`: ```bpl -(+↢3)⁻ 10 ⍝ 7 ++↢3∘⁻10 ⍝ 7 ``` -Without the parentheses, `+↢3⁻` tries to invert the number `3`, which is a `DOMAIN` error. A space also groups the bound function: `+↢3 ⁻ 10` gives `7`. The signed superscript `3⁻¹` still means one third; `f⁻¹` on a function now means rank ¯1. +`(+↢3)⁻10` and `+↢3 ⁻ 10` also give `7`. Without a boundary, `+↢3⁻` tries to invert the number `3`, which is a `DOMAIN` error. The signed superscript `3⁻¹` still means one third; `f⁻¹` on a function now means rank ¯1. ## Under -[Under](glyphs/at.qmd), `f@g`, applies `f` to what `g` selects and writes the result back. `(10×)@[1 3]` multiplies items 1 and 3 by 10. `⌊@(10×)` rounds down to tenths: it scales by 10, rounds down, then scales back. +[Under](glyphs/at.qmd), `f@g`, applies `f` to what `g` selects and writes the result back. `10×∘@[1 3]` multiplies items 1 and 3 by 10. `⌊@(10×)` rounds down to tenths: it scales by 10, rounds down, then scales back. ```bpl -(10×)@[1 3] 1 2 3 4 ⍝ 1 20 3 40 +10×∘@[1 3] 1 2 3 4 ⍝ 1 20 3 40 ⌊@(10×)1.25 2.78 ⍝ 1.2 2.7 ``` @@ -122,8 +134,8 @@ Within a run, an operator's right operand is the one [token](terms.qmd#term-toke ≥⍣3⁝5 ⍝ 8 m←["r":2 "c":3]ρι6ₓ ◇ i←"c" ◇ +/⍠i⁝m ⍝ ["r":2]ρ[3 12]ₓ m←["r":2 "c":3]ρι6ₓ ◇ i←"c" ◇ 10÷√1++/⍠i⁝m ⍝ ["r":2]ρ[5 2.773500981] -(2×)⍣3⁝5 ⍝ 40 -(+/÷≢)⍤1⁝2 3ρι6 ⍝ 1 4 +2×∘⍣3⁝5 ⍝ 40 ++/÷≢∘⍤1⁝2 3ρι6 ⍝ 1 4 ``` Add no separator where the tokens already stay distinct. A name never joins a literal strand, as in `f⍣n1`. A vector argument can touch its operand in brackets, as in `≥⍣2[1 2 3]`. @@ -133,19 +145,19 @@ Add no separator where the tokens already stay distinct. A name never joins a li ```bpl w←[1 2◇3 4◇5 6] ◇ 10 20+⍤1⁝w ⍝ [11 22◇13 24◇15 26] w←[1 2◇3 4◇5 6] ◇ 10 20(+⍤1)w ⍝ [11 22◇13 24◇15 26] -m←2 3ρι6 ◇ (+/÷≢)⍤1m ⍝ 1 4 +m←2 3ρι6 ◇ +/÷≢∘⍤1m ⍝ 1 4 ``` ## Forms that read worse -Each pair gives the same result. +Each pair gives the same result with one argument. | Reads worse | Reads better | |---|---| | `{(+/ω)÷≢ω}` | `+/÷≢` | | `0.5×⊢+2÷⊢` | `⊽⁻+÷` | -| `{ω÷+/ω}` | `⊢÷+/` | -| `{ω×10}@[1 3]` | `(10×)@[1 3]` | +| `{ω÷+/ω}` | `÷↢ +/` | +| `{ω×10}@[1 3]` | `10×∘@[1 3]` | | `{+/ω}⍠0` | `+/₀` | | `{ω,1}` | `,↢1` | | `×⍨x` | `x²` | diff --git a/nbs/data.ipynb b/nbs/data.ipynb index 25930f8a..4dd1c344 100644 --- a/nbs/data.ipynb +++ b/nbs/data.ipynb @@ -1011,7 +1011,7 @@ } ], "source": [ - "words←' '(≠⊆⊢)\"to be or not to be\"\n", + "words←' ' ≠⊆⊢ \"to be or not to be\"\n", "u←∪words\n", "words←[uιwords]⌷u\n", "words" diff --git a/nbs/evaluation.qmd b/nbs/evaluation.qmd index bd9ae050..35b69844 100644 --- a/nbs/evaluation.qmd +++ b/nbs/evaluation.qmd @@ -49,6 +49,8 @@ Operators bind before functions apply. An operator's left [operand](terms.qmd#te Each run counts as one token, as a parenthesised group does. An operator at the end of a run takes the next run as its right operand, as it takes a touching token: in `⌊@ 10× x`, the right operand of `@` is the run `10×`. An operator at the start of a run takes the previous run as its left operand: in `≢⊢ ⌸x`, the left operand of `⌸` is the run `≢⊢`. Names that hold operators count in both places. +Before an operator, `∘` finishes the whole current run as its left operand: `⊂⊢∘⌸` is `(⊂⊢)⌸`. Spaces keep earlier functions and left arguments outside that run. Before a function, `∘` remains Atop. [Operators on trains](composition.qmd#operators-on-trains) gives examples. + ```bpl m←2 3ρι6ₓ ◇ +/⍤1 m ⍝ [3 12]ₓ m←2 3ρι6ₓ ◇ +/⍤1m ⍝ [3 12]ₓ diff --git a/nbs/glyphs/at.qmd b/nbs/glyphs/at.qmd index 914db5d2..3a101fd0 100644 --- a/nbs/glyphs/at.qmd +++ b/nbs/glyphs/at.qmd @@ -43,8 +43,8 @@ When a function `g` returns items of `Y` that selecting primitives took, `f@g Y` +\@∊ [3 1 0] [2 5] ⍝ [[3 4 4] [6 11]] 9@↑ 1 2 3 ⍝ 9 2 3 -@ 1↓⍣2 [1 2 3 4] ⍝ 1 2 ¯3 ¯4 -(ι≢)@< 30 10 20 ⍝ [2 0 1]ₓ -(1+)@{ω>15 # ω} 10 20 30 ⍝ 10 21 31 +ι≢∘@< 30 10 20 ⍝ [2 0 1]ₓ +1+∘@{ω>15 # ω} 10 20 30 ⍝ 10 21 31 ``` `g` may select a keyed or unkeyed position more than once. Which replacement that position keeps isn't promised. The updated array retains its keys. A write at a fill position is dropped. diff --git a/nbs/glyphs/circle-star.qmd b/nbs/glyphs/circle-star.qmd index 4b91b97e..9e65cdba 100644 --- a/nbs/glyphs/circle-star.qmd +++ b/nbs/glyphs/circle-star.qmd @@ -24,7 +24,7 @@ Uses complex logarithms where needed. `⊛0` is `¯∞`. `1⊛1` gives zero when ```bpl 2⊛⁻3 ⍝ 8 -(⊛↢8)⁻3 ⍝ 2 +⊛↢8∘⁻3 ⍝ 2 ``` ## Fast combinations @@ -33,7 +33,7 @@ BPL computes this combination by an algorithm of its own, in any spelling. [Trai | Combination | Applies to | Benefit | |---|---|---| -| `⊛1+Y`, `⊛Y+1` or `⊛∘(1+)` | real `Y` | log1p keeps the digits of a small `Y` that `1+Y` rounds away | +| `⊛1+Y`, `⊛Y+1`, `⊛1+` or `⊛∘(1+)` | real `Y` | log1p keeps the digits of a small `Y` that `1+Y` rounds away | At an item below ¯1 under dynamic kinds, and on complex numbers, `⊛1+Y` computes `⊛` of `1+Y`. Softplus, `⊛1+*Y`, stays accurate for a very negative `Y`: diff --git a/nbs/glyphs/comma.qmd b/nbs/glyphs/comma.qmd index 9e345e71..195929cc 100644 --- a/nbs/glyphs/comma.qmd +++ b/nbs/glyphs/comma.qmd @@ -31,5 +31,5 @@ x,⍤0 y ⍝ [1 3◇2 4] ```bpl ,⁻1 2 3 ⍝ 1 2 3 1 2,⁻1 2 3 4 ⍝ 3 4 -(,↢3 4)⁻1 2 3 4 ⍝ 1 2 +,↢3 4∘⁻1 2 3 4 ⍝ 1 2 ``` diff --git a/nbs/glyphs/dot.qmd b/nbs/glyphs/dot.qmd index 7315f219..5fdea058 100644 --- a/nbs/glyphs/dot.qmd +++ b/nbs/glyphs/dot.qmd @@ -58,18 +58,18 @@ m.[⍸m>9] ⍝ [10 11]ₓ After a function, `.name` and `.[I]` read from the function's result, as they read from an array: `f.name` is `{(α f ω).name}`, and `f.[I]` is `{(α f ω).[I]}`, with one argument or two. A path goes on from there, as in `⊢.x.[0]`. A name that holds a function, or a path to one, gives an inner product instead, as in `+.ops.mul`. [Under](at.qmd) writes back through the read. +`.[I]` by itself is a selector function, equivalent to `⊢.[I]`. Ordinary operators can modify it: `.[I]¨` selects from each item. + ```bpl v←10 20 30 40 -⊢.[[1 3]] v ⍝ 20 40 +.[[1 3]] v ⍝ 20 40 1 2 3 4 +.[[0 2]] v ⍝ 11 33 -(10×)@ ⊢.[[1 3]] v ⍝ 10 200 30 400 +10×∘@.[[1 3]]v ⍝ 10 200 30 400 T←["a":1 "x":[10 20 30]] ⊢.a¨[T T] ⍝ 1 1 ⊢.x.[[2 0]] T ⍝ 30 10 ``` -`.[I]` by itself is a selector function, equivalent to `⊢.[I]`. Ordinary operators can modify it: `.[I]¨` selects from each item. - `T.[I]` applies those operators to the selector before calling it on `T`. The first bracket at the current level starts the selectors. Parentheses and dfns keep their contents together. Put a bracket-valued operator operand in parentheses, as in `T.⍤([0 1])[I]`. Numeric superscripts specify Rank in this position. ```bpl diff --git a/nbs/glyphs/jot-diaeresis.qmd b/nbs/glyphs/jot-diaeresis.qmd index a398754f..831bacf1 100644 --- a/nbs/glyphs/jot-diaeresis.qmd +++ b/nbs/glyphs/jot-diaeresis.qmd @@ -34,12 +34,12 @@ v←[[1 2] [3 4 5]] -⍤0⁝5 ⍝ ¯5 ``` -With rank `∞`, the cell is the whole argument, and rank `¯∞` is rank 0. For example, `⍤1 ∞` pairs each row on the left with the whole right argument. In the examples below, `(+⌿×)⍤1 ∞` is the matrix-vector product. With a matrix on the right, the same function multiplies matrices. +With rank `∞`, the cell is the whole argument, and rank `¯∞` is rank 0. For example, `⍤1 ∞` pairs each row on the left with the whole right argument. In the examples below, `+⌿×∘⍤1 ∞` is the matrix-vector product. `∘` gives Rank the whole train `+⌿×` as its left operand. With a matrix on the right, the same function multiplies matrices. ```bpl m←[1 2 3◇4 5 6] -m (+⌿×)⍤1 ∞ [1 2 3] ⍝ 14 32 -m (+⌿×)⍤1 ∞ [1 10◇2 20◇3 30] ⍝ [14 140◇32 320] +m +⌿×∘⍤1 ∞ [1 2 3] ⍝ 14 32 +m +⌿×∘⍤1 ∞ [1 10◇2 20◇3 30] ⍝ [14 140◇32 320] ``` A function right operand computes the ranks from the arguments, as BQN's `⎉` does: `f⍤g Y` is `f⍤(g Y) Y`, and `X f⍤g Y` is `X f⍤(X g Y) Y`. [Axis](quad-colon.qmd) and [Stencil](quad-diamond.qmd) take a function right operand in the same way. Here each cell of `Y` with the rank of `X` pairs with `X`: diff --git a/nbs/glyphs/jot.qmd b/nbs/glyphs/jot.qmd index 1d8a8975..f7423dea 100644 --- a/nbs/glyphs/jot.qmd +++ b/nbs/glyphs/jot.qmd @@ -9,6 +9,16 @@ Dyadic operator. Keys: `Alt-t`. Hanzi: `承` ``` See [Rank](jot-diaeresis.qmd) for applying a function to cells of a chosen rank. +## Before an operator + +`∘` before an operator finishes the current run as its left operand. `⊂⊢∘⌸` is `(⊂⊢)⌸`; `+/÷≢∘⍤1` applies the mean train at rank 1. Named operators have the same behavior. Spaces or parentheses keep any earlier function or left argument outside the run. + +```bpl +m←2 3ρι6 ◇ +/÷≢∘⍤1m ⍝ 1 4 +``` + +Before a function, `∘` still means Atop. [Composing functions](../composition.qmd#operators-on-trains) describes both uses. + ## Inverse `(f∘g)⁻Y` is `g⁻ f⁻ Y`. `X(f∘g)⁻Y` is `X g⁻ f⁻ Y`. diff --git a/nbs/glyphs/left-tail-arrow.qmd b/nbs/glyphs/left-tail-arrow.qmd index e11ec32e..6d16e7ad 100644 --- a/nbs/glyphs/left-tail-arrow.qmd +++ b/nbs/glyphs/left-tail-arrow.qmd @@ -29,6 +29,6 @@ See [Before](right-tail-arrow.qmd), [Atop / Rank](jot-diaeresis.qmd) and [Over]( With an array operand, `(f↢X)⁻Y` inverts `f` with `X` fixed on the right. With two functions, `X(f↢g)⁻Y` inverts `f` with `X` fixed, then applies `g⁻`. The hook `(×↢*)⁻` gives the Lambert W function. ```bpl -(-↢3)⁻7 ⍝ 10 -(×↢*)⁻1 ⍝ 0.567143290409784 +-↢3∘⁻7 ⍝ 10 +×↢*∘⁻1 ⍝ 0.567143290409784 ``` diff --git a/nbs/glyphs/minus.qmd b/nbs/glyphs/minus.qmd index c4323acd..e73d837d 100644 --- a/nbs/glyphs/minus.qmd +++ b/nbs/glyphs/minus.qmd @@ -29,5 +29,5 @@ Characters shift by code point. Character differences are exact integers. ```bpl 10-⁻3 ⍝ 7 -(-↢3)⁻7 ⍝ 10 +-↢3∘⁻7 ⍝ 10 ``` diff --git a/nbs/glyphs/obelus.qmd b/nbs/glyphs/obelus.qmd index be961541..e9464504 100644 --- a/nbs/glyphs/obelus.qmd +++ b/nbs/glyphs/obelus.qmd @@ -34,7 +34,7 @@ An integer divided by zero gives the integer limit of its sign: i64's under dyna ```bpl 12÷⁻4 ⍝ 3 -(÷↢4)⁻3 ⍝ 12 +÷↢4∘⁻3 ⍝ 12 ``` ## Errors diff --git a/nbs/glyphs/partial.qmd b/nbs/glyphs/partial.qmd index 506e466d..f73366a2 100644 --- a/nbs/glyphs/partial.qmd +++ b/nbs/glyphs/partial.qmd @@ -6,7 +6,7 @@ Monadic operator. Keys: `Alt-f`. Hanzi: `导/梯` (`導/梯`) ```bpl ⊼∂3 ⍝ 6 -(+/⊼)∂ 1 2 3 ⍝ 2 4 6 ++/⊼ ∂ 1 2 3 ⍝ 2 4 6 cart←{[r t]←ω ◇ [r×2○t r×1○t]} ◇ -.×cart∂ 2 0.5 ⍝ 2 3 {α×ω×ω}∂ 2 ⍝ 12 ``` @@ -15,7 +15,7 @@ cart←{[r t]←ω ◇ [r×2○t r×1○t]} ◇ -.×cart∂ 2 0.5 ⍝ 2 ```bpl ⊼∂3ₓ ⍝ 6 -p←["a":1 "b":2] ◇ (⊢.a×⊢.b)∂ p ⍝ ["a":2 "b":1] +p←["a":1 "b":2] ◇ ⊢.a×⊢.b∘∂ p ⍝ ["a":2 "b":1] -∂3ₓ₁ ⍝ ¯1ₛ ``` diff --git a/nbs/glyphs/pi.qmd b/nbs/glyphs/pi.qmd index bdf2f984..4c0a4220 100644 --- a/nbs/glyphs/pi.qmd +++ b/nbs/glyphs/pi.qmd @@ -25,5 +25,5 @@ Use [`○`](circle.qmd) to turn an angle into a unit-circle point. ```bpl π⁻π2 ⍝ 2 2π⁻4 ⍝ π0.5 -(π↢2)⁻4 ⍝ 8÷π1 +π↢2∘⁻4 ⍝ 8÷π1 ``` diff --git a/nbs/glyphs/quad-equal.qmd b/nbs/glyphs/quad-equal.qmd index 092cb382..d03f2023 100644 --- a/nbs/glyphs/quad-equal.qmd +++ b/nbs/glyphs/quad-equal.qmd @@ -21,8 +21,10 @@ BPL computes each of these combinations by an algorithm of its own, in any spell | `≢⊢ ⌸Y` or `K (≢⊢)⌸Y` | any keys | counts each group's major cells without building the groups | | `K (f/⊢)⌸Y` or `K (f⌿⊢)⌸Y`, with `f` of `+ ⌈ ⌊`, or `×` of floats | a vector `Y` of floats, integers or Booleans | folds every group in one pass, without a call of the operand for each group | +`∘` also gives Key a whole train as its operand, as in `≢⊢∘⌸Y` or `K +/⊢∘⌸ Y`. + ```bpl -≢⊢ ⌸"banana" ⍝ [1 3 2]ₓ -1 1 2(+/⊢)⌸10 20 30 ⍝ 30 30 -(•explain "1 1 2(+/⊢)⌸10 20 30").fast.rule ⍝ ["group folds"] +≢⊢∘⌸"banana" ⍝ [1 3 2]ₓ +1 1 2 +/⊢∘⌸ 10 20 30 ⍝ 30 30 +(•explain "1 1 2 +/⊢∘⌸ 10 20 30").fast.rule ⍝ ["group folds"] ``` diff --git a/nbs/glyphs/radical.qmd b/nbs/glyphs/radical.qmd index dedd1e1b..ebb58d19 100644 --- a/nbs/glyphs/radical.qmd +++ b/nbs/glyphs/radical.qmd @@ -24,7 +24,7 @@ Function. Keys: `Alt-3`. Hanzi: `根`. Ranks: `0` monadic, `0 0` dyadic ```bpl √⁻3 ⍝ 9 3√⁻2 ⍝ 8 -(√↢8)⁻2 ⍝ 3 +√↢8∘⁻2 ⍝ 3 ``` ## Errors diff --git a/nbs/glyphs/right-tail-arrow.qmd b/nbs/glyphs/right-tail-arrow.qmd index 87b811c0..f8e4c0fe 100644 --- a/nbs/glyphs/right-tail-arrow.qmd +++ b/nbs/glyphs/right-tail-arrow.qmd @@ -30,6 +30,6 @@ See [After](left-tail-arrow.qmd), [Atop / Rank](jot-diaeresis.qmd) and [Over](ci With an array operand, `(X↣f)⁻Y` is `X f⁻ Y`. With two functions, `X(f↣g)⁻Y` inverts `g` with `f X` fixed on the left. ```bpl -(2↣×)⁻6 ⍝ 3 +2↣×∘⁻6 ⍝ 3 3(-↣+)⁻7 ⍝ 10 ``` diff --git a/nbs/glyphs/star.qmd b/nbs/glyphs/star.qmd index eb4f25e4..e09ae37f 100644 --- a/nbs/glyphs/star.qmd +++ b/nbs/glyphs/star.qmd @@ -33,7 +33,7 @@ See also function [power `⍣`](star-diaeresis.qmd). ```bpl 2*⁻8 ⍝ 3 -(*↢2)⁻9 ⍝ 3 +*↢2∘⁻9 ⍝ 3 ``` ## Fast combinations diff --git a/nbs/glyphs/superscript-minus.qmd b/nbs/glyphs/superscript-minus.qmd index 4912474a..da5fcbfe 100644 --- a/nbs/glyphs/superscript-minus.qmd +++ b/nbs/glyphs/superscript-minus.qmd @@ -7,7 +7,7 @@ Monadic operator. Keys: `Alt-_`. Hanzi: `反` ```bpl *⁻1 ⍝ 0 2×⁻10 ⍝ 5 -(+↢3)⁻10 ⍝ 7 ++↢3∘⁻10 ⍝ 7 ``` BPL infers inverses of the primitives and compositions listed under [Power](star-diaeresis.qmd). `f⁻←g` [attaches an inverse](../assignment.ipynb#inverses-and-derivatives) to a named function. @@ -18,6 +18,6 @@ The minus is bare: no superscript digit follows it. `f⁻¹` means [Rank](jot-di ⌽⁻ ¹[1 2◇3 4] ⍝ [2 1◇4 3] ``` -An inverse applies to the whole derived function on its left. Use parentheses or a space when that function ends in a number: `(+↢3)⁻10` or `+↢3 ⁻ 10`. +An inverse applies to the whole derived function on its left. Use `∘` before `⁻` when that function ends in a number: `+↢3∘⁻10`. Parentheses or a space also group the function: `(+↢3)⁻10` or `+↢3 ⁻ 10`. Errors: `DOMAIN` for an unknown inverse or an array operand. diff --git a/nbs/glyphs/tricolon.qmd b/nbs/glyphs/tricolon.qmd index af11e38d..a1f73f32 100644 --- a/nbs/glyphs/tricolon.qmd +++ b/nbs/glyphs/tricolon.qmd @@ -9,4 +9,4 @@ h←- ◇ m←5 ◇ h⁝m ⍝ ¯5 ≥⍣3⁝5 ⍝ 8 ``` -`h⁝m` is `h(m)`, and `≥⍣3⁝5` is `(≥⍣3)5`. A space also groups runs, as [Syntax](../syntax.qmd#runs) describes. `⁝` leaves the grouping unchanged. Parentheses still group compound operands and trains within a run. [Composing functions](../composition.qmd#separating-an-operand-from-its-argument) gives examples. +`h⁝m` is `h(m)`, and `≥⍣3⁝5` is `(≥⍣3)5`. A space also groups runs, as [Syntax](../syntax.qmd#runs) describes. `⁝` leaves the grouping unchanged. Before an operator, [`∘`](jot.qmd#before-an-operator) finishes the current run as its left operand. Parentheses still group compound operands and nested trains. [Composing functions](../composition.qmd#separating-an-operand-from-its-argument) gives examples. diff --git a/nbs/index-notation.qmd b/nbs/index-notation.qmd index 7d3a8509..cacf895a 100644 --- a/nbs/index-notation.qmd +++ b/nbs/index-notation.qmd @@ -61,7 +61,7 @@ heads←{h←0 2 1 3⍉ω ◇ (¯2↓ρh , ×/¯2↑ρh)ρh} attn←{ [wqkv wo]←α [q k v]←1 3 0 2 4⍉ω+.×wqkv - s←mask(+⍤2) q+.×⍤2⍉⍤2⁝k ÷ √⊢/ρq + s←mask(+⍤2) q+.×⍤2⍉⍤2⁝k ÷ √(ρq)₋₁ e←*s-⌈/s (heads e÷+/e +.×⍤2 v)+.×wo } diff --git a/nbs/indexing.ipynb b/nbs/indexing.ipynb index 97c49842..210d82ac 100644 --- a/nbs/indexing.ipynb +++ b/nbs/indexing.ipynb @@ -985,7 +985,7 @@ } ], "source": [ - "(10×)@[1 3]u" + "10×∘@[1 3]u" ] }, { @@ -993,7 +993,7 @@ "id": "2f2d7b49", "metadata": {}, "source": [ - "After a function, `.[I]` selects from its result. Under can use `⊢.[I]` as its selector, with no dfn:" + "After a function, `.[I]` selects from its result. Standalone `.[I]`, equivalent to `⊢.[I]`, builds a selector function that Under can use directly:" ] }, { @@ -1013,7 +1013,7 @@ } ], "source": [ - "(10×)@ ⊢.[[1 3]] u" + "10×∘@.[[1 3]] u" ] }, { diff --git a/nbs/llms.txt b/nbs/llms.txt index 4c3fddd4..77bc4396 100644 --- a/nbs/llms.txt +++ b/nbs/llms.txt @@ -40,7 +40,7 @@ Entry: Dyalog diff, example, defining page. Index, not reference: read a feature - [Glyphs](glyphs.html.md): every glyph, name. page `glyphs/.html.md`: read before use. - [Removed](glyphs.html.md#language-wide-differences-from-dyalog-apl): `Y[I]` indexing, `f[A]` axes, `∘.f`, windowed reduce, laminate. - [`@`](glyphs/at.html.md) Under: `f@g`: `f` on `g`'s selection, written back (BQN `⌾`, not Dyalog `@`): `⌊@(10×)1.25 2.78`→`1.2 2.7`. positions/`[p]`: left arg aligns with right by position, by key if both keyed: `10 20 30 +@[0 2] 1 2 3`→`11 2 33`. -- [`∘`](glyphs/jot.html.md) atop: `α f∘g ω`≡`f α g ω` (BQN; Dyalog `α f g ω`): `10 -∘| ¯3`→`¯7`. +- [`∘`](glyphs/jot.html.md) atop: `α f∘g ω`≡`f α g ω` (BQN; Dyalog `α f g ω`): `10 -∘| ¯3`→`¯7`. Before an operator, finishes the whole current run as its left operand: `⊂⊢∘⌸`≡`(⊂⊢)⌸`, `+/÷≢∘⍤1`≡`(+/÷≢)⍤1`. Spaces keep earlier functions and left arguments outside that run. - [`⍤`](glyphs/jot-diaeresis.html.md) rank, only: `+/¹m` (`+/⍤1 m`) row sums. fn right operand computes ranks. - [`⍠`](glyphs/quad-colon.html.md) axis: along axes by number or name: `+/⍠0 m`, `+/⍠"month" sales`. options record sets fill: `5↑⍠["fill":¯1] 1 2`→`1 2 ¯1 ¯1 ¯1`. - [`⌺`](glyphs/quad-diamond.html.md) stencil: Rank over centred windows: `f⌺3 v`≡`f⍤¯1 [¯3]↕v`; `life⌺[3 3] g`≡`life⍤¯2 [¯3 ¯3]↕g`. `f` gets each window alone, no `α` (J `;._3`). fn right operand computes window from args. diff --git a/nbs/mathish.qmd b/nbs/mathish.qmd index 94323e5d..b6dd52fd 100644 --- a/nbs/mathish.qmd +++ b/nbs/mathish.qmd @@ -40,7 +40,7 @@ x÷|x ⍝ ¯1 0 1 Normalising counts is another useful example: ```bpl -shares←⊢÷+/ +shares←÷↢ +/ shares [2 3 5] ⍝ 0.2 0.3 0.5 shares [0 0 0] ⍝ 0 0 0 ``` diff --git a/nbs/python.ipynb b/nbs/python.ipynb index 7989d389..a4b9ef0f 100644 --- a/nbs/python.ipynb +++ b/nbs/python.ipynb @@ -2017,7 +2017,11 @@ "source": [ "Python commas separate parts without ending a BPL run. `space` ends the run explicitly. `fn(plus.reduce, divide, tally)` builds `+/÷≢`; adding `space` before data applies that whole train. An already-built function is one part without adding `space`.\n", "\n", - "Function attributes also build trains. `plus.reduce.divide.tally` is the three-part train `+/÷≢`, not a succession of atops. Calling a function with one function argument composes them: `sqrt(plus.reduce / tally)` is `√∘(+/÷≢)`." + "Function attributes append functions to a train. `plus.reduce.divide.tally` builds the three-part train `+/÷≢`.\n", + "\n", + "An operator attribute applies to the whole function built so far. In `sqrt.plus.reduce`, `.reduce` applies to the two-function train `√+`. The full chain `sqrt.plus.reduce.divide.tally` means `√+∘/÷≢`, equivalently `(√+)/÷≢`: reduce with `√+`, then divide by the count.\n", + "\n", + "Calling a function with one function argument composes the functions. `sqrt(plus.reduce / tally)` means `√+/÷≢`, the square root of the mean. On `[4., 4.]`, composition gives `2`, while the dot chain gives `√2`." ] }, { @@ -2029,7 +2033,7 @@ { "data": { "text/plain": [ - "2" + "(Array(2.0), Array(1.4142135623730951))" ] }, "execution_count": null, @@ -2041,7 +2045,52 @@ "mean_train = plus.reduce.divide.tally\n", "assert mean_train([2., 4., 9.]).py == 5.\n", "assert fn(plus.reduce, divide, tally, space, [2., 4., 9.]).py == 5.\n", - "sqrt(plus.reduce / tally)([4., 4.])" + "composed = sqrt(plus.reduce / tally)([4., 4.])\n", + "chained = sqrt.plus.reduce.divide.tally([4., 4.])\n", + "assert b.match([composed, chained], [2., sqrt(2.)]).py\n", + "composed, chained" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9fda51a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bpl('(√+/÷≢)4 4')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35033901", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2 2" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bpl('2 2')" ] }, { @@ -2285,7 +2334,7 @@ "id": "802ab031", "metadata": {}, "source": [ - "`f.power.each(n)` constructs `(f⍣n)¨`, just like `f.power(n).each`. For multiple missing operands, successive calls fill the innermost construction first: `f.power.each.power(n)(m)` constructs `((f⍣n)¨)⍣m`. Complete the operands before passing evaluation arguments.\n", + "`f.power.each(n)` constructs `f⍣n∘¨` (equivalently `(f⍣n)¨`), just like `f.power(n).each`. For multiple missing operands, successive calls fill the innermost construction first: `f.power.each.power(n)(m)` constructs `f⍣n∘¨∘⍣m`. Complete the operands before passing evaluation arguments.\n", "\n", "Arithmetic between functions makes forks: `f+g` is `(f+g)`. `f @ g` is inner product. `f << g` is the Atop `f∘g`, and `f >> g` is `g∘f`. `f ** n` is Power. `atop(f, g, h)` is `f << g << h`; it accepts two or more functions. Use `fork(f, g, h)` for a fork. `value >> (f >> g)` calls the composed function once. `value >> f >> g` makes separate calls and misses whole-composition optimizations. `|` binds arguments to functions; array `|` remains bitwise OR. Python's precedence applies when building these expressions:" ] @@ -2395,9 +2444,9 @@ " \n", " \n", " \n", - " cake\n", - " 5\n", - " 4.25\n", + " tea\n", + " 2\n", + " 3.5\n", " \n", " \n", "\n", @@ -2406,7 +2455,7 @@ "text/plain": [ " qty price\n", "item \n", - "cake 5 4.25" + "tea 2 3.5" ] }, "execution_count": null, @@ -3011,7 +3060,7 @@ "id": "735a5794", "metadata": {}, "source": [ - "`basedpl.syntax.linear` gives source in its full linear form: the ordinary APL that BPL reads, with its [grouping](syntax.qmd#runs) shown. Strands of literals appear in brackets. Runs beside a run-ending dot keep their spacing. Other runs of several tokens appear in parentheses, as does a literal that touches the name before it, such as the `1` in `x1`. `basedpl.worker.fresh` shows the linear form before each result, as the timing and checking tools in `basedpl.bpltests` do, so unexpected grouping shows up beside the code:" + "`basedpl.syntax.linear` shows BPL source with its structural [grouping](syntax.qmd#runs) made explicit. Operator-stage `∘` keeps its spelling because operator names resolve during evaluation. Strands of literals appear in brackets. Runs beside a run-ending dot keep their spacing. Other runs of several tokens appear in parentheses, as does a literal that touches the name before it, such as the `1` in `x1`. `basedpl.worker.fresh` shows the linear form before each result, as the timing and checking tools in `basedpl.bpltests` do, so unexpected grouping shows up beside the code:" ] }, { diff --git a/nbs/scripts.ipynb b/nbs/scripts.ipynb index cf8af6dd..18c3ad5f 100644 --- a/nbs/scripts.ipynb +++ b/nbs/scripts.ipynb @@ -621,7 +621,7 @@ "id": "3ed12e5e", "metadata": {}, "source": [ - "A space before the superscript also works:" + "`∘` before an operator finishes the whole function run: `+↢3∘⁻10` also gives `7`. A space before the superscript works too:" ] }, { diff --git a/nbs/study/01_basics.ipynb b/nbs/study/01_basics.ipynb index de48a404..e9933708 100644 --- a/nbs/study/01_basics.ipynb +++ b/nbs/study/01_basics.ipynb @@ -2618,7 +2618,7 @@ { "data": { "text/plain": [ - "1 2 2 1 2" + "2 5 3 3 3" ] }, "metadata": {}, @@ -2638,7 +2638,7 @@ { "data": { "text/plain": [ - "0.1214016233 0.9272420518" + "0.4217883456 0.4926436415" ] }, "metadata": {}, @@ -2674,7 +2674,7 @@ { "data": { "text/plain": [ - "42 33 50 10 20 0 29 3 39 15 2 49 45" + "16 27 1 34 37 29 3 49 5 28 24 2 8" ] }, "metadata": {}, diff --git a/nbs/study/03_operators.ipynb b/nbs/study/03_operators.ipynb index d653dc7f..0b34de9b 100644 --- a/nbs/study/03_operators.ipynb +++ b/nbs/study/03_operators.ipynb @@ -2490,7 +2490,7 @@ "id": "10d5153b", "metadata": {}, "source": [ - "### `∘` Atop" + "### Atop" ] }, { @@ -2498,7 +2498,7 @@ "id": "1d61412e-1612-4414-8b9b-9d7832614717", "metadata": {}, "source": [ - "Atop applies the function on the right, then applies the function on the left to its result. `f∘g y` is `f(g y)`; with two arguments, `x f∘g y` is `f(x g y)`." + "Two functions in a train make an atop. It applies the function on the right, then applies the function on the left to its result. `f⁝g y` is `f(g y)`. With two arguments, `x(f⁝g)y` is `f(x g y)`." ] }, { @@ -2508,7 +2508,7 @@ "metadata": {}, "outputs": [], "source": [ - "f←*∘÷" + "f←*÷" ] }, { @@ -2556,6 +2556,8 @@ "id": "5ed53671", "metadata": {}, "source": [ + "The operator `∘` spells an atop explicitly: `*∘÷` is the same function as `*÷`.\n", + "\n", "Atop's inverse undoes its two steps in reverse order. Here negating a reciprocal is undone by negating, then taking the reciprocal." ] }, @@ -2676,7 +2678,7 @@ "id": "ca6378d6", "metadata": {}, "source": [ - "Inverses apply to bound functions too. `2↣×⁻` undoes doubling. A numeric superscript touching a final number instead applies to that number: `+↢3⁻¹` adds a third. Bare `+↢3⁻` tries to invert the number and raises `DOMAIN`. Use parentheses, or a space before `⁻`, to invert the whole bound function." + "Inverses apply to bound functions too. `2↣×⁻` undoes doubling. A numeric superscript touching a final number instead applies to that number: `+↢3⁻¹` adds a third. Bare `+↢3⁻` tries to invert the number and raises `DOMAIN`. Put `∘` before `⁻` to invert the whole function run. Parentheses and a space before `⁻` also work." ] }, { @@ -2715,7 +2717,7 @@ ], "source": [ "2↣×⁻10\n", - "(+↢3)⁻10\n", + "+↢3∘⁻10\n", "+↢3 ⁻ 10" ] }, @@ -2753,8 +2755,8 @@ } ], "source": [ - "(,↢3 4)⁻1 2 3 4\n", - "(⍪↢3 4)⁻[1 2◇3 4]" + ",↢3 4∘⁻1 2 3 4\n", + "⍪↢3 4∘⁻[1 2◇3 4]" ] }, { @@ -2988,7 +2990,7 @@ } ], "source": [ - "(×↢*)⁻1" + "×↢*∘⁻1" ] }, { @@ -3521,7 +3523,7 @@ } ], "source": [ - "(inc⍣3)⁻10" + "inc⍣3∘⁻10" ] }, { diff --git a/nbs/study/05_searching-indexing.ipynb b/nbs/study/05_searching-indexing.ipynb index a7e08732..85862b9b 100644 --- a/nbs/study/05_searching-indexing.ipynb +++ b/nbs/study/05_searching-indexing.ipynb @@ -1800,7 +1800,7 @@ "id": "edd0f754", "metadata": {}, "source": [ - "After a function, `.name` and `.[...]` select from its result. Same, `⊢`, returns its argument unchanged, so these read its fields and positions. The suffix also works after a dyadic function:" + "After a function, `.name` and `.[...]` select from its result. Same, `⊢`, returns its argument unchanged, so `⊢.address.city` reads that field. A standalone `.[I]` is a selector function. The suffix also works after a dyadic function:" ] }, { @@ -1840,7 +1840,7 @@ } ], "source": [ - "⊢.[[1 3]] v" + ".[[1 3]] v" ] }, { diff --git a/nbs/study/06_rearranging-arrays.ipynb b/nbs/study/06_rearranging-arrays.ipynb index 3f421ee1..1ca95592 100644 --- a/nbs/study/06_rearranging-arrays.ipynb +++ b/nbs/study/06_rearranging-arrays.ipynb @@ -1488,7 +1488,7 @@ "id": "53d53df1", "metadata": {}, "source": [ - "Dot selections after Same can supply Under's selector without a dfn. Here the selected positions are multiplied by ten; a field selector changes just that field:" + "A standalone `.[I]` can supply Under's selector. Here the selected positions are multiplied by ten. A field selector such as `⊢.qty` changes just that field:" ] }, { @@ -1509,7 +1509,7 @@ ], "source": [ "v←10 20 30 40\n", - "(10×)@ ⊢.[[1 3]] v" + "10×∘@.[[1 3]] v" ] }, { @@ -1558,7 +1558,7 @@ } ], "source": [ - "(≥@[1 3])⁻10 20 30 40" + "≥@[1 3]∘⁻10 20 30 40" ] }, { @@ -1594,7 +1594,7 @@ } ], "source": [ - "abs←(0≤)⍚[- ⊢]\n", + "abs←0≤∘⍚[- ⊢]\n", "abs¯3" ] }, @@ -1643,7 +1643,7 @@ } ], "source": [ - "(0≤)⍚[0 √] ¯4 9 16" + "0≤∘⍚[0 √] ¯4 9 16" ] }, { diff --git a/nbs/style.qmd b/nbs/style.qmd index 42363ba7..fd147bd1 100644 --- a/nbs/style.qmd +++ b/nbs/style.qmd @@ -6,7 +6,7 @@ Use spaces to show the top-level structure of an expression. Keep parentheses wh A function between two runs takes a space on each side, as in `a×b + c`. A space on one side only changes the grouping: `a×b +c` is the strand `[a×b ; +c]`, and `a×b+ c` applies the train `a×b+` to `c`. -`⁝` separates a derived function's operand from its right argument where a boundary is needed: `f⍣3⁝0`, `10÷√1++/⍠i⁝m`, `(+/÷≢)⍤1⁝2 3ρι6`. It stops literal stranding without grouping the expression before it. A vector argument can touch the operand in brackets: `≥⍣2[1 2 3]`. Add nothing where tokens already stay distinct, as in `+/⍤1m` or `(+/÷≢)⍠d⊼n`. +`⁝` separates a derived function's operand from its right argument where a boundary is needed: `f⍣3⁝0`, `10÷√1++/⍠i⁝m`, `+/÷≢∘⍤1⁝2 3ρι6`. It stops literal stranding without grouping the expression before it. A vector argument can touch the operand in brackets: `≥⍣2[1 2 3]`. Add nothing where tokens already stay distinct, as in `+/⍤1m` or `+/÷≢∘⍠d⊼n`. With a left argument as well, `⁝` works the same way, as in `Q+.×⍠e⁝r`. Parentheses around the derived function also show it between its arguments: `10 20(+⍤1)m`. Use whichever reads better. Keep parentheses where a compound operand or nested train needs grouping, as in `⌊@(10×)x`. @@ -16,7 +16,7 @@ Lowercase `j` reads most clearly in complex literals, as in `3j4`. Uppercase `J` Brackets build a list from names or expressions. A bare literal list, such as `1 2 3`, is a shorthand. In brackets, a list of names reads as a list at a glance, as in `[a b]`, even where `a b` would also work. `[n 2]ρ0`, `[x],y`, `[a◇b]` and `f⍤1 m` read more directly than APL's `(n,2)ρ0`, `(⊂x),y`, `⊃[a b]` and `⊃f¨↓m`. Where no item of a bracket list contains a space, spaces separate the items more lightly than `;`: `[i j]` rather than `[i;j]`. -An operand often reads best touching its operator, with parentheses around a compound operand, as in `⌊@(10×)x` and `(≢⊢)⌸x`. Spacing also makes a run into an operand: after a dyadic operator and a space, the whole next run is the operand, as in `⌊@ 10× x`. When both operands are compound, parentheses around each show the grouping: `(•ucs 9)@(⍸tabs)ω`. An operand keeps its parentheses when a left argument comes before it, as in `tree (α⍢)/ next`. Report any case where none of these forms reads well. +Before an operator, `∘` finishes the whole current run as its left operand: `≢⊢∘⌸x`, `+/÷≢∘⍤1m`. Keep parentheses where they show nested structure more clearly. A compound right operand keeps its grouping, as in `⌊@(10×)x`. Spacing also makes a run into an operand: after a dyadic operator and a space, the whole next run is the operand, as in `⌊@ 10× x`. When both operands are compound, parentheses around each show the grouping: `(•ucs 9)@(⍸tabs)ω`. An operand can keep its parentheses when a left argument comes before it, as in `tree (α⍢)/ next`. Report any case where none of these forms reads well. A train reads more directly than naming an argument twice: `+/÷≢ v` rather than `+/v ÷ ≢v`. A tacit form usually reads more directly than a dfn. A dfn reads better where the tacit form would be longer or clearly awkward. diff --git a/nbs/syntax.qmd b/nbs/syntax.qmd index 9993cd03..99acbab6 100644 --- a/nbs/syntax.qmd +++ b/nbs/syntax.qmd @@ -1,6 +1,6 @@ # Syntax -BPL divides each statement into runs and [tokens](terms.qmd#term-token) before it evaluates anything. The rules on this page don't depend on what any name holds. [Evaluation](evaluation.qmd) covers how the runs then bind. [Types of brackets](bracket-types.ipynb) and the [Language overview](overview.ipynb) explain the same rules with more examples. +BPL divides each statement into runs and [tokens](terms.qmd#term-token) before it evaluates anything. This page describes that structure and the operator boundaries between runs. [Evaluation](evaluation.qmd) covers how the runs bind. [Types of brackets](bracket-types.ipynb) and the [Language overview](overview.ipynb) explain the same rules with more examples. ## Hanzi spellings @@ -60,13 +60,13 @@ A literal strand can appear on each side. In `+/⍠2 3⁝3 3↕m`, the operand o ## Runs -Outside brackets, spaces divide a statement into [runs](terms.qmd#term-run). A group in brackets, parentheses or braces counts as one token, even when it contains spaces. Apart from the two operator rules below, each run must give one value: a subject, a function, or a lone operator such as the `/` in `+ / x`. A run that gives anything else is a `SYNTAX` error. +Outside brackets, spaces divide a statement into [runs](terms.qmd#term-run). A group in brackets, parentheses or braces counts as one token, even when it contains spaces. The operator rules below can join runs or finish an operand within one. Each completed run must give one value: a subject, a function, or a lone operator such as the `/` in `+ / x`. A run that gives anything else is a `SYNTAX` error. BPL reads a statement in three steps: 1. Literals separated by spaces join into strands, such as `1 2` or `"ab" "cd"`. 2. Each remaining run is wrapped in parentheses. -3. The result reads as ordinary APL, right to left. +3. Bind the result from right to left, applying the operator rules below. So `a+b × c+d` is `(a+b)(×)(c+d)`, and `1 2+3` is `(1 2)+3`, not `(1)(2+3)`. Spaces around a function between two runs decide the grouping. `a×b + c` is `(a×b)(+)(c)`, the sum of `a×b` and `c`. `a×b +c` is `(a×b)(+c)`, a strand of two subjects. `a×b+ c` is `(a×b+)(c)`, the train `a×b+` applied to `c`. @@ -74,6 +74,8 @@ A call's result binds where a subject would, whatever it returns, and so do a fi A run that ends in a dyadic operator takes the next run as its right operand: `⌊@ 10× x` is `(⌊@)(10×)(x)`, where `10×` is the operand of `@`. A run that starts with an operator takes the previous run as its left operand: `≢⊢ ⌸x` is `(≢⊢)(⌸x)`, where `≢⊢` is the operand of `⌸`. [Evaluation](evaluation.qmd) explains operands. +Before an operator, `∘` finishes the current run as its left operand: `⊂⊢∘⌸` is `(⊂⊢)⌸`. This depends on what the next item holds, like the operator rules that join runs. Before a function, `∘` remains Atop. [Composing functions](composition.qmd#operators-on-trains) gives examples. + ```bpl a←1 ◇ b←2 ◇ c←3 ◇ d←4 ◇ a+b × c+d ⍝ 21 (1)(+)(1) ⍝ 2 diff --git a/nbs/system/replace.qmd b/nbs/system/replace.qmd index e81f8c94..9462b6b9 100644 --- a/nbs/system/replace.qmd +++ b/nbs/system/replace.qmd @@ -4,7 +4,7 @@ ```bpl "ab12 cd3" •replace ["([a-z]+)([0-9]+)" "$2:$1"] ⍝ "12:ab 3:cd" -(•replace↢["[0-9]+" "#"])¨"a1" "b22" ⍝ "a#" "b#" +•replace↢["[0-9]+" "#"]∘¨"a1" "b22" ⍝ "a#" "b#" ``` Errors: `DOMAIN` for invalid patterns, non-text arguments, and a right argument that isn't a pattern and a template; `SYNTAX` for the wrong valence; `LIMIT` for oversized results. diff --git a/python/basedpl/syntax.py b/python/basedpl/syntax.py index 158d7d07..a3a2229b 100644 --- a/python/basedpl/syntax.py +++ b/python/basedpl/syntax.py @@ -47,7 +47,7 @@ def node(kind, start, end, children, error): return node(*_parse(text)) def linear(text): - "The full linear form of BPL `text`: the ordinary APL that BPL reads. Literal strands are in brackets. Runs of several tokens are in parentheses, except beside a run-ending dot. A bracket list whose items hold several tokens has ` ; ` between its items. A literal that touches the name before it, as the `1` in `x1` does, is in parentheses." + "Show BPL's structural grouping. Literal strands are in brackets and multi-token runs in parentheses, except beside a run-ending dot. Multi-token bracket items use ` ; `. A literal touching a name, as in `x1`, is parenthesized. Operator-stage `∘` stays explicit: operator names resolve at evaluation." dotted = lambda n: n and n.kind == 'Run' and n.children[-1].text == "." def form(n, before=None): if n.kind in ('Comment', 'Error'): return n.text diff --git a/src/eval/binder.rs b/src/eval/binder.rs index 723645e7..0a415e58 100644 --- a/src/eval/binder.rs +++ b/src/eval/binder.rs @@ -7,6 +7,11 @@ use crate::{ }; use std::{borrow::Cow, sync::Arc}; +fn stage_marker(node: &Node) -> bool { + matches!(node.kind, NodeKind::Operator(OperatorKind::Atop)) + || matches!(&node.kind, NodeKind::Embedded(Value::Operator(op)) if matches!(*op.0, OperatorNode::Primitive(OperatorKind::Atop))) +} + impl Session { /// Lower selector sections and data-first operator tails before runs bind their operands. fn sections<'a>(&self, nodes: &'a [Node]) -> Cow<'a, [Node]> { @@ -108,9 +113,25 @@ impl Session { _ => members, } } - /// Rewrite dot reads. + /// Finish the current run before an operator: `⊂⊢∘⌸` is `(⊂⊢)⌸`. Names and dot paths use their resolved categories. + fn stages<'a>(&self, nodes: &'a [Node]) -> Cow<'a, [Node]> { + let boundary = |i: usize| stage_marker(&nodes[i]) && nodes.get(i + 1).is_some_and(|next| self.static_category(next).valence().is_some()); + if !(1..nodes.len()).any(&boundary) { return Cow::Borrowed(nodes); } + let (mut out, mut start) = (Vec::with_capacity(nodes.len()), 0); + for (i, node) in nodes.iter().enumerate() { + // Selector lowering can flatten runs; an operator still takes the run before it as its operand. + if i > 0 && !crate::syntax::touching(&nodes[i - 1], node) && self.static_category(node).valence().is_none() { start = out.len(); } + if boundary(i) && out.len() > start { + let inner = out.split_off(start); + out.push(Node { span: crate::syntax::cover(&inner), kind: NodeKind::Group(inner) }); + } else { out.push(node.clone()); if matches!(node.kind, NodeKind::Assign) { start = out.len(); } } + } + Cow::Owned(out) + } + + /// Rewrite dot reads and operator-stage boundaries. pub(super) fn members_with<'a>(&self, nodes: &'a [Node]) -> Cow<'a, [Node]> { - if !Self::has_members(nodes) { return Cow::Borrowed(nodes); } + if !Self::has_members(nodes) { return self.stages(nodes); } let mut out: Vec = Vec::with_capacity(nodes.len()); // Whether the last node in `out` is the right operand of a `.` after a function. let (mut i, mut on_function) = (0, false); @@ -190,7 +211,7 @@ impl Session { node.kind = NodeKind::Operator(OperatorKind::Super(Superscript::Mark(crate::number::Mark::Rational))); } } - Cow::Owned(out) + match self.stages(&out) { Cow::Borrowed(_) => Cow::Owned(out), Cow::Owned(staged) => Cow::Owned(staged) } } /// Whether a run ends in a dyadic operator, which then takes the next run as its right operand. @@ -206,15 +227,25 @@ impl Session { /// Join each run that starts with an operator to the node before it, as the run's first token: `≢⊢ ⌸x` reads as `(≢⊢)⌸x`. A run before it joins as a group. A joined run that still starts with an operator joins again: `+ / ¨x` reads as `+/¨x`. No join reaches across `←`. fn joined<'a>(&self, nodes: &'a [Node]) -> Cow<'a, [Node]> { let starts = |node: &Node| matches!(&node.kind, NodeKind::Run(inner) if self.starts_with_operator(inner)); - if !nodes.iter().skip(1).any(starts) { return Cow::Borrowed(nodes); } + let joins = |before: &Node, node: &Node| { + starts(node) + || matches!(&before.kind, NodeKind::Run(inner) if inner.last().is_some_and(stage_marker)) && self.static_category(node).valence().is_some() + }; + if !nodes.windows(2).any(|w| joins(&w[0], &w[1])) { return Cow::Borrowed(nodes); } let mut out: Vec = Vec::with_capacity(nodes.len()); for node in nodes { let mut node = node.clone(); - while out.last().is_some_and(|before| !matches!(before.kind, NodeKind::Assign)) && starts(&node) { + while out.last().is_some_and(|before| !matches!(before.kind, NodeKind::Assign) && joins(before, &node)) { let before = out.pop().expect("a node is before this run"); let span = Span { source: before.span.source.clone(), range: before.span.range.start..node.span.range.end }; - let before = match before.kind { NodeKind::Run(run) => Node { kind: NodeKind::Group(run), span: before.span }, _ => before }; - let NodeKind::Run(inner) = node.kind else { unreachable!() }; + let before = match before.kind { + NodeKind::Run(mut run) => { + if run.last().is_some_and(stage_marker) { run.pop(); } + Node { span: crate::syntax::cover(&run), kind: NodeKind::Group(run) } + } + _ => before, + }; + let inner = match node.kind { NodeKind::Run(inner) => inner, _ => vec![node] }; node = Node { kind: NodeKind::Run(std::iter::once(before).chain(inner).collect()), span }; } out.push(node); diff --git a/tests/core.rs b/tests/core.rs index ca2440f3..141dec1c 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -510,6 +510,25 @@ fn operator_categories_and_singleton_replicate() { } } +#[test] +fn operator_stage_boundaries() { + let s = Session::new(); + s.eval("w←1 2 3 ◇ m←2 3ρι6 ◇ op←⌸ ◇ ops←[\"key\":⌸]"); + for (stage, grouped) in [ + ("0 1 0 ⊂⊢∘⌸ \"abc\"", "0 1 0 (⊂⊢)⌸ \"abc\""), ("+/÷≢∘⍤1⁝m", "(+/÷≢)⍤1⁝m"), ("+/÷≢∘¹m", "(+/÷≢)¹m"), + ("+↢3∘⁻10", "(+↢3)⁻10"), ("+/w×∘∂ 4 5 6", "(+/w×)∂ 4 5 6"), ("custom←{⍶ω} ◇ -÷∘custom 2", "(-÷){⍶ω} 2"), + ("0 1 0 ⊂⊢∘op \"abc\"", "0 1 0 (⊂⊢)op \"abc\""), ("0 1 0 ⊂⊢∘ops.key \"abc\"", "0 1 0 (⊂⊢)ops.key \"abc\""), + ("+/÷≢∘⍤1∘¨[m m]", "((+/÷≢)⍤1)¨[m m]"), ("-÷∘∘+ 2", "(-÷)∘+ 2"), ("+/÷≢∘ ⍤1⁝m", "(+/÷≢)⍤1⁝m"), + ("+/÷≢ ∘⍤1⁝m", "(+/÷≢)⍤1⁝m"), ("f←+/÷≢∘⍤1 ◇ f m", "(+/÷≢)⍤1⁝m"), ("√ +/÷≢∘⍤1⁝m", "√ (+/÷≢)⍤1⁝m"), + ("≢ -÷ ∘ ¨ 1 4 9", "3ₓ"), ("≢ +/÷≢ ∘ ⍤ 1 m", "2ₓ"), ("T←[1 2;3 4] ◇ i←[0] ◇ ≢ -÷∘¨T.¨[i]", "2ₓ"), + ] { + let expected = evaluated(&s, grouped).unwrap(); + assert_same(evaluated(&s, stage), &expected); + } + equiv_in(&s, "√∘+/1 3 5", "(√+)/1 3 5"); + assert!(s.eval("∘⌸").error.is_some()); +} + #[test] fn long_assignment_chains() { assert_same(Session::new().eval(&format!("{}7", "a←".repeat(10_000))).value, &float(7.0)); } diff --git a/tests/reference/aplcart.bpl b/tests/reference/aplcart.bpl index c0a08843..9a42d268 100644 --- a/tests/reference/aplcart.bpl +++ b/tests/reference/aplcart.bpl @@ -1502,7 +1502,7 @@ N←3 1 3 2 3 ◇ ⌈⌿↣=N ⍝ $t $f $t $f $t ⍝ aplcart/table.tsv:745 — Duplicate Y cells where indicated by Av Av←1 0 1 Y←3 2ρι6ₓ -Av+↢1↣#Y ⍝ [0 1◇0 1◇2 3◇4 5◇4 5]ₓ +(Av+1)#Y ⍝ [0 1◇0 1◇2 3◇4 5◇4 5]ₓ ⍝ aplcart/table.tsv:746 — Using Boolean array A for expanding Yv (Yv's elements at 1s in A) A←$t $f $t $f $t @@ -1510,7 +1510,7 @@ Yv←[2 3 4]ₓ Yv@[⊢] +A ⍝ [2 0 3 0 4]ₓ ⍝ aplcart/table.tsv:747 — Initialise a matrix with Js columns and no rows -Js←4 ◇ 0↣,↣ρJs ⍝ 0 4ρ0 +Js←4 ◇ [0 Js]ρ0 ⍝ 0 4ρ0 ⍝ aplcart/table.tsv:748 — Are any true? B←[1 0 0◇0 0 0] ◇ ∨/∘,B ⍝ 1 @@ -1530,7 +1530,7 @@ Y←[1 2;3 4] [X]⌷⊃Y ⍝ 2 3 ⍝ aplcart/table.tsv:753 — Continued fraction convergents with terms N; Reduce each prefix to preserve right association -N←2 3 4 ◇ (+↢÷/)¨,\N ⍝ 2 2.333333333333333 2.307692307692307 +N←2 3 4 ◇ +↢÷/∘¨,\N ⍝ 2 2.333333333333333 2.307692307692307 ⍝ aplcart/table.tsv:754 — Turn off all 1s before first 0 (remove leading 1s) B←1 1 0 1 0 1 0 ◇ ∧\↣B ⍝ $f $f $f $f $t $f $f ⍝ aplcart/table.tsv:759 — Transpose matrix Ym on condition Bs Bs←$t Ym←2 3ρι6ₓ -Bs⌽↢0 1↣⍉Ym ⍝ [0 3◇1 4◇2 5]ₓ +(Bs⌽0 1)⍉Ym ⍝ [0 3◇1 4◇2 5]ₓ ⍝ aplcart/table.tsv:760 — Count of occurrences of each unique major cell Y←3 1 3 2 ◇ ⊢↢≢⌸Y ⍝ [2 1 1]ₓ @@ -1576,10 +1576,10 @@ Yv←4 5 6 [Xv◇Yv] ⍝ [1 2 3◇4 5 6] ⍝ aplcart/table.tsv:766 — Are characters of D lowercase? -D←"Abc 19 Σς!" ◇ 1↣•c↣≠D ⍝ $f $t $t $f $f $f $f $f $t $f +D←"Abc 19 Σς!" ◇ 1•c≠⊢ D ⍝ $f $t $t $f $f $f $f $f $t $f ⍝ aplcart/table.tsv:767 — Are characters of D uppercase? -D←"Abc 19 Σς!" ◇ ¯1↣•c↣≠D ⍝ $t $f $f $f $f $f $f $t $f $f +D←"Abc 19 Σς!" ◇ ¯1•c≠⊢ D ⍝ $t $f $f $f $f $f $f $t $f $f ⍝ aplcart/table.tsv:768 — Apply Z as constant function if array, but like normal dyadic function if function X←1 @@ -1962,7 +1962,7 @@ Ym←2 3ρι6ₓ ◇ ι∘≢↣,Ym ⍝ [0 0 1 2◇1 3 4 5]ₓ ⍝ aplcart/table.tsv:870 — Starting points for Y in indices pointed by Iv Iv←2 1 Y←3 1 3 2 -Iv∊↢ι↢≢Y ⍝ $t $t +Iv∊ι≢Y ⍝ $t $t ⍝ aplcart/table.tsv:871 — Sort Ascending Y←3 1 3 2 ◇ ⊂∘⍋↣⌷Y ⍝ 1 2 3 3 @@ -1992,7 +1992,7 @@ Ym←2 3ρι6ₓ ◇ ⌽∘⍉∘⌽Ym ⍝ [5 2◇4 1◇3 0]ₓ ⍝ aplcart/table.tsv:881 — Matrix with Is rows, each consisting of Yv Is←2 Yv←4 5 6 -Is#↢⍉↢⍪Yv ⍝ [4 5 6◇4 5 6] +Is#⍉⍪Yv ⍝ [4 5 6◇4 5 6] ⍝ aplcart/table.tsv:882 — Number of occurrences of scalar Xs in array Y Xs←9 @@ -2378,7 +2378,7 @@ J←3 8 15 ◇ 2↣⊥⁻J ⍝ [0 0 1 1◇1 0 0 0◇1 1 1 1] Nm←[0 1◇¯1 0] ◇ ⍉≡⌹ Nm ⍝ $t ⍝ aplcart/table.tsv:985 — Show all digits of integer Js (unknown digits as “_”) -[3.41252E10;(0↣⍕) 3.41252E10] ⍝ 34125200000 "34125200000" +[3.41252E10;0↣⍕ 3.41252E10] ⍝ 34125200000 "34125200000" ⍝ aplcart/table.tsv:986 — Vertically stack digits of ιIs (helps locating column positions); optional {X} instantiated as dyadic use Js←4 ◇ (1 0⍕⍉∘(10 10⊤ι))Js ⍝ ["0000"◇"0123"] @@ -2474,7 +2474,7 @@ N←4 9 16 ◇ π4×*↢2 N 201.06192982974676 1017.8760197630929 3216.990877275948 ⍝ aplcart/table.tsv:1022 — Cube Root -N←4 9 16 ◇ *↢÷↢3 N +N←4 9 16 ◇ 3√N 1.5874010519681994 2.080083823051904 2.5198420997897464 ⍝ aplcart/table.tsv:1023 — Probabilistic NOR @@ -2683,7 +2683,7 @@ Bv←$f $f $t $t $f ◇ ⊥⍨0=⌽ Bv ⍝ 2ₓ ⍝ aplcart/table.tsv:1078 — Future value of cash flows N at interest Ms Ms←0.1 N←100 200 300 -Ms 1↣+↣⊥ N ⍝ 641 +(1+Ms)⊥N ⍝ 641 ⍝ aplcart/table.tsv:1079 — Base-Is digit sum Is←10 @@ -2697,7 +2697,7 @@ Is +/⊥⁻ J ⍝ 6 9 7 ⍝ aplcart/table.tsv:1081 — Decoding numeric codes J packed with field widths Iv (ZYYYZZZ:1 3 2) Iv←1 3 2 J←123456 987654 -Iv 10↣*↣⊤ J ⍝ [1 234 56◇9 876 54] +(10*Iv)⊤J ⍝ [1 234 56◇9 876 54] ⍝ aplcart/table.tsv:1086 — Convert letters to their positions in the alphabet D←"Abc 19 Σς!" ◇ ($aι1↣•c)D ⍝ [0 1 2 26 26 26 26 26 26 26]ₓ @@ -2797,10 +2797,10 @@ r←1=↢¿ρ↢2 Jv Jv≡ρr ∧ ∧/,r∊0 1 ⍝ $t ⍝ aplcart/table.tsv:1110 — Jacobsthal-Lucas number -Js←4 ◇ 2↣*+¯1↣* Js ⍝ 17 +Js←4 ◇ 2*+¯1* Js ⍝ 17 ⍝ aplcart/table.tsv:1111 — Hartley kernel -N←4 9 16 ◇ 1↣○+2↣○ N +N←4 9 16 ◇ 1○+2○ N ¯1.4104461161715403 ¯0.49901177664292035 ¯1.24556279698845 ⍝ aplcart/table.tsv:1112 — Residue after dividing N by M but replacing 0 with M @@ -3025,7 +3025,7 @@ Y←2 3ρι6ₓ ◇ -∘≢↑¯1↣↓ Y ⍝ [0 0 0◇0 1 2]ₓ ⍝ aplcart/table.tsv:1173 — Resign: Transfer of sign from M to N M←¯1 0 1 N←2 ¯3 4 -M ×↢×⍨↢| N ⍝ ¯2 0 4 +×M × |N ⍝ ¯2 0 4 ⍝ aplcart/table.tsv:1174 — Normalisation by Infinity-norm (⊢÷⌈⌿∘|) 3 4 ⍝ 0.75 1 @@ -3287,7 +3287,7 @@ Is ×ρ(1↑⍨↢-⊢) Js ⍝ 0 0 0 1 0 0 0 1 ⍝ aplcart/table.tsv:1248 — Increasing absolute value without change of sign M←1 N←¯3 0 2 -M ⊢↢××+↢| N ⍝ ¯4 0 3 +×N × M+|N ⍝ ¯4 0 3 ⍝ aplcart/table.tsv:1249 — Jacobsthal number Js←4 ◇ 3÷⍨(2↣*-¯1↣*) Js ⍝ 5 @@ -3941,9 +3941,9 @@ Yv←ι6ₓ Is ⍉⊢ρ⍨≢↣÷⍨,⊣ Yv ⍝ [0 2 4◇1 3 5]ₓ ⍝ aplcart/table.tsv:1424 — Distribute major cells of Y into Is (default Is:≢Y) groups as evenly as possible -UnZip ← |↢ι↢≢⊢↢⊂⌸⊢ +UnZip ← |↢(ι≢) ⊂⊢∘⌸ ⊢ var ← "abcdef" -[1 UnZip var;2 UnZip var;3 UnZip var;4 UnZip var;5 UnZip var;6 UnZip var;7 UnZip var;8 UnZip var;(|∘ι∘≢⊢↢⊂⌸⊢) var] +[1 UnZip var;2 UnZip var;3 UnZip var;4 UnZip var;5 UnZip var;6 UnZip var;7 UnZip var;8 UnZip var;(ι≢ ⊂⊢∘⌸ ⊢) var] ⍝ => [["abcdef"] ["ace" "bdf"] ["ad" "be" "cf"] ["ae" "bf" "c" "d"] ["af" "b" "c" "d" "e"] ["a" "b" "c" "d" "e" "f"] ["a" "b" "c" "d" "e" "f"] ["a" "b" "c" "d" "e" "f"] ["a" "b" "c" "d" "e" "f"]] @@ -7803,7 +7803,7 @@ Ym←[1 "ab"◇2 "cd"◇3 "ef"] ◇ ⊂₀Ym [1 2 3;"ab" "cd" "ef"] ⍝ aplcart/tt.tsv:362 — Corner element of a (non-empty) array Y[1;1;1…] -Y←1+2 3ρι6ₓ ◇ ↑∘,Y ⍝ 1 +Y←1+2 3ρι6ₓ ◇ ↑,Y ⍝ 1 ⍝ aplcart/tt.tsv:376 — Count of trailing ones Bv←$t $f $t $t ◇ ⊥⍨Bv ⍝ 2ₓ @@ -7881,10 +7881,14 @@ Y←[1 [2 3] 4] ◇ ,¨Y ⍝ [[1] [2 3] [4]] ⍝ aplcart/tt.tsv:530 — Euclidean distance between two points in N-space Mv←1 2 3 Nv←4 6 3 -Mv *↢÷↢2∘(1↣⊥)∘(*↢2)∘- Nv ⍝ 5 +distance←√+/ ⊼- +Mv distance Nv ⍝ 5 ⍝ aplcart/tt.tsv:532 — Euclidean distance table for points in N-space (one point per row) -Nm←[0 0◇3 0◇3 4] ◇ (*↢÷↢2∘(1↣⊥)∘(*↢2)∘-)⍤1⍤1 99⍨Nm +distance←√+/ ⊼- +Nm←[0 0◇3 0◇3 4] +distance⍤1⍤1 99⍨Nm +⍝ => [0 3 5◇3 0 4◇5 4 0] ⍝ aplcart/tt.tsv:536 — Euler's totient function (fastest up to about 1000) @@ -8137,7 +8141,7 @@ Bm←[$f $t $f◇$f $f $t◇$t $f $f] ◇ ~0∊,∘ ×∘|≤≤⊗⍨∘ι∘ Bm←[$f $t $f◇$f $f $t◇$t $f $f] ◇ ~0∊,∘ ×∘|≤<⊗⍨∘ι∘≢ Bm ⍝ $f ⍝ aplcart/tt.tsv:898 — Is Dv a palindrome? -Dv←"racecar" ◇ ⌽≡⊢ Dv ⍝ $t +Dv←"racecar" ◇ ≡↢⌽ Dv ⍝ $t ⍝ aplcart/tt.tsv:921 — Is Js a deficient number?; Reuse concrete inputs from aplcart/table.tsv:1384; execute this alternate recipe independently Js←8 ◇ +⍨>(1⊥↢∪⊢∨ι) Js ⍝ $t @@ -8426,7 +8430,7 @@ N←¯2 0 3 ◇ 0↣≥N ⍝ $t $t $f N←¯2 0 3 ◇ 0↣≠N ⍝ $t $f $t ⍝ aplcart/tt.tsv:1268 — Normalisation by Infinity-norm -N←¯3 4 ◇ ⊢÷⌈⌿∘| N ⍝ ¯0.75 1 +N←¯3 4 ◇ ÷↢(⌈⌿|) N ⍝ ¯0.75 1 ⍝ aplcart/tt.tsv:1270 — Normalisation by Is-norm Is←2 @@ -8434,7 +8438,7 @@ N←3 4 Is ⊢÷⊣*↢÷⍨(1⊥*⍨↢|) N ⍝ 0.6 0.8 ⍝ aplcart/tt.tsv:1274 — Normalise N so that sum is 1 -N←4 9 16 ◇ ⊢÷+⌿ N +N←4 9 16 ◇ ÷↢(+⌿) N 0.1379310344827586 0.3103448275862069 0.5517241379310345 ⍝ aplcart/tt.tsv:1280 — Not all true? @@ -8609,16 +8613,16 @@ Y←3 1 3 2 f⍨↢g⍨Y ⍝ 5 4 4 5 ⍝ aplcart/tt.tsv:1564 — Rotate 180° -Ym←2 3ρι6ₓ ◇ ⌽∘⊖Ym ⍝ [5 4 3◇2 1 0]ₓ +Ym←2 3ρι6ₓ ◇ ⌽⊖Ym ⍝ [5 4 3◇2 1 0]ₓ ⍝ aplcart/tt.tsv:1566 — Rotate 90° clockwise -Ym←2 3ρι6ₓ ◇ ⌽∘⍉Ym ⍝ [3 0◇4 1◇5 2]ₓ +Ym←2 3ρι6ₓ ◇ ⌽⍉Ym ⍝ [3 0◇4 1◇5 2]ₓ ⍝ aplcart/tt.tsv:1568 — Rotate 90° counter-clockwise -Ym←2 3ρι6ₓ ◇ ⍉∘⌽Ym ⍝ [2 5◇1 4◇0 3]ₓ +Ym←2 3ρι6ₓ ◇ ⍉⌽Ym ⍝ [2 5◇1 4◇0 3]ₓ ⍝ aplcart/tt.tsv:1590 — Rounding to nearest integer (favouring up) -N←¯2.5 ¯1.2 0.5 2.5 ◇ ⌊0.5+⊢ N ⍝ [¯2 ¯1 1 3]ₓ +N←¯2.5 ¯1.2 0.5 2.5 ◇ ⌊0.5+N ⍝ [¯2 ¯1 1 3]ₓ ⍝ aplcart/tt.tsv:1591 — Rounding to nearest odd number (favouring away from 0) N←¯4 ¯2 0 2 4 ◇ ××↢⌊|+(1>2||) N ⍝ [¯5 ¯3 0 3 5]ₓ @@ -9540,11 +9544,11 @@ f2021 1 1 12 34 56 123 2020 53 5 12 34 56 123000 ⍝ aplcart/table.tsv:653 — Convert (⎕TS-style or Dyalog date number) date-times to microsecond precision ⎕TS-style time-stamps; Prepared existing-array arithmetic with independently captured date components; Explicit timestamp: ISO week uses the year of its Thursday; subsecond components are rescaled arithmetically; No clock or general date-conversion API needed -(1000↣×)@6 [2024 2 29 12 34 56 123] +1000↣×∘@6 [2024 2 29 12 34 56 123] 2024 2 29 12 34 56 123000 ⍝ aplcart/table.tsv:655 — Convert (⎕TS-style or Dyalog date number) date-times to nanosecond precision ⎕TS-style time-stamps; Prepared existing-array arithmetic with independently captured date components; Explicit timestamp: ISO week uses the year of its Thursday; subsecond components are rescaled arithmetically; No clock or general date-conversion API needed -(1000000↣×)@6 [2024 2 29 12 34 56 123] +1000000↣×∘@6 [2024 2 29 12 34 56 123] 2024 2 29 12 34 56 123000000 ⍝ aplcart/table.tsv:2188 — Trace of function application; Prepared tracing dop using first-class function values and explicit output; no function-source introspection required; Trace ordinary function values rather than reconstructing a function name through ⎕OR. Preserve monadic/dyadic calls and return the operand result @@ -9676,7 +9680,7 @@ T←•csv "name,score",(•ucs 10),"Ada,12",(•ucs 10),"Ben,7" {⍬:⍬}¨ι3ₓ ⍝ [(⍬:⍬) (⍬:⍬) (⍬:⍬)] ⍝ aplcart/table.tsv:1515 — Header-row matrix to records -{K←↑ω ◇ (K↣:)¨↓1↓ω}["Name" "Age" "Zipcode"◇"Bob" "21" "30102"◇"Sally" "32" "43001"] +{K←↑ω ◇ K↣:∘¨↓1↓ω}["Name" "Age" "Zipcode"◇"Bob" "21" "30102"◇"Sally" "32" "43001"] [["Name":"Bob" "Age":"21" "Zipcode":"30102"] ["Name":"Sally" "Age":"32" "Zipcode":"43001"]] ⍝ aplcart/table.tsv:1891 — Name–value pairs to a keyed record @@ -9741,7 +9745,7 @@ T.[["aa" "bb"]]←1 2 T ⍝ "aa" "bb":1 2 ⍝ aplcart/table.tsv:2167 — Apply a function to every record value -(×↢2)¨["price":[10 20] "qty":[2 4]] ⍝ ["price":[20 40] "qty":[4 8]] +×↢2∘¨["price":[10 20] "qty":[2 4]] ⍝ ["price":[20 40] "qty":[4 8]] ⍝ aplcart/table.tsv:2320 — Apply a function stored in a record T←["increment":{ω+1}] ◇ T.increment 5 ⍝ 6 diff --git a/tests/reference/april_mathish.bpl b/tests/reference/april_mathish.bpl index 28f3f942..8a452507 100644 --- a/tests/reference/april_mathish.bpl +++ b/tests/reference/april_mathish.bpl @@ -62,7 +62,7 @@ cfract¯2.3 ⍝ [¯3 1 2 3]ₓ ⍝ april/libraries/dfns/numeric/demo.lisp:211 — Real rational recovery uses the first continued-fraction convergent within relative error 1e-14. Pi recovers 5419351/1725033, whose canonical continued fraction ends in 4. April's 14-column float-round-trip expectation is retained separately; 0+ makes coefficients approximate [cfract: rational:]←•load "lib/numeric.bpl" -rational (+↢÷)/¨,\ 0+(0 cfract π1) +rational +↢÷∘/¨,\ 0+(0 cfract π1) ⍝ => [3 22 333 355 103993 104348 208341 312689 833719 1146408 5419351◇1 7 106 113 33102 33215 66317 99532 265381 364913 1725033] diff --git a/tests/reference/bqn.bpl b/tests/reference/bqn.bpl index ea97efee..92d6ca47 100644 --- a/tests/reference/bqn.bpl +++ b/tests/reference/bqn.bpl @@ -27,11 +27,11 @@ fn←"rtd"ι↑ ⍚[⌽ 1↑ 1↓ ⊢] ◇ [fn "r123";fn "d123";fn "123"] +\@∊ [3 1 0] [2 5] [0 0 6] ⍝ (3 4 4◇6 11◇11 11 17) ⍝ bqn:under:61 — Rotate the first column -a←[0 1 2◇3 4 5◇6 7 8◇9 10 11] ◇ (1⌽)@(↑⍤1) a +a←[0 1 2◇3 4 5◇6 7 8◇9 10 11] ◇ 1⌽∘@(↑⍤1) a [3 1 2◇6 4 5◇9 7 8◇0 10 11] ⍝ bqn:under:69 — Reverse the first half of a list -⌽@ (2÷⍨≢)↑⊢ "abcdef" ⍝ "cbadef" +⌽@(2÷⍨≢∘↣↑)"abcdef" ⍝ "cbadef" ⍝ bqn:under:73 — Add 10 to the elements below 5, with the mask computed from the argument 10+@[5>] 3 8 2 2 6 ⍝ 13 8 12 12 6 @@ -43,7 +43,7 @@ a←[0 1 2◇3 4 5◇6 7 8◇9 10 11] ◇ (1⌽)@(↑⍤1) a +/÷≢ @(×⍨) 2 3 4 5 ⍝ 3.6742346141747673 ⍝ bqn:under:99 — An arithmetic progression up to a limit, through an inverted transformation -ι∘⌈@((4+3×⊢)⁻) 20 ⍝ 4 7 10 13 16 19 +ι∘⌈@(4+3×⊢∘⁻) 20 ⍝ 4 7 10 13 16 19 ⍝ bqn:under:109 — Replace selected positions with the same positions of the left argument "abcd" ⊣@([[1 2]]⌷) "0123" ⍝ "0bc3" @@ -56,7 +56,7 @@ a←[0 1 2◇3 4 5◇6 7 8◇9 10 11] ◇ (1⌽)@(↑⍤1) a ["ab" "cde" "fg"] ⊣@∊ ["---" "----"] ⍝ "abc" "defg" ⍝ bqn:under:123 — Add to the selected elements, keeping the others -(1 2 3+)@(1 1 0 1#) 10 20 30 40 ⍝ 11 22 30 43 +1 2 3+∘@(1 1 0 1#) 10 20 30 40 ⍝ 11 22 30 43 ⍝⍝ Undo @@ -67,7 +67,7 @@ a←[0 1 2◇3 4 5◇6 7 8◇9 10 11] ◇ (1⌽)@(↑⍤1) a ×⍨⁻ ×⍨ ¯3 ⍝ 3 ⍝ bqn:undo:36 — Undo multiplication by a bound left argument -(3×)⁻ 12 ⍝ 4 +3×∘⁻ 12 ⍝ 4 ⍝ bqn:undo:51 — Undo Left accepts arguments that already match 3 ⊣⁻ 3 ⍝ 3 @@ -78,7 +78,7 @@ a←[0 1 2◇3 4 5◇6 7 8◇9 10 11] ◇ (1⌽)@(↑⍤1) a 3 (+⍣2) 7 ⍝ 13 ⍝ bqn:repeat:39 — Halve the numbers above 6, using a computed count of 0 or 1 -{(÷↢2)⍣(6<ω)ω}¨ 3 7 2 1 8 ⍝ 3 3.5 2 1 4 +{÷↢2∘⍣(6<ω)ω}¨ 3 7 2 1 8 ⍝ 3 3.5 2 1 4 ⍝ bqn:repeat:49 — A negative count applies the inverse 1 (⌽⍣¯1) "abcde" ⍝ "eabcd" @@ -187,10 +187,10 @@ m←[0 1 0◇¯1 0 0◇0 0 1] ◇ m (+⌿∘×)⍤[1 ∞] 1 2 3×⊗1 10 ⍝⍝ Fold ⍝ bqn:fold:115 — A continued fraction for e -(+↢÷)/2 1 2 1 1 4 1 1 ⍝ 2.7183098591549295 ++↢÷∘/2 1 2 1 1 4 1 1 ⍝ 2.7183098591549295 ⍝ bqn:fold:139 — A seeded fold starts from the right end -"STOP" (⌽↣,)/ ["ABCDE" "012" "abcd"] ⍝ "EDCBA210dcbaSTOP" +"STOP" ⌽↣,∘/ ["ABCDE" "012" "abcd"] ⍝ "EDCBA210dcbaSTOP" ⍝ bqn:fold:158 — Reducing an empty first axis gives one identity per cell +⌿0 4ρ0 ⍝ 0 0 0 0 diff --git a/tests/reference/core.bpl b/tests/reference/core.bpl index 6963de4d..d507bd00 100644 --- a/tests/reference/core.bpl +++ b/tests/reference/core.bpl @@ -274,7 +274,7 @@ s←⍉[0 $n ; :]⌷⍠["fill":2]3 100ρ1 [3 ; $t ; 3] ⍝ fill-option-under — Under passes its function the chosen fill at each position that its selection fills, and writes back only the positions that the selection reads from its argument. A take after another selection reads its own fill, and two takes that choose different fills each give theirs -[(⊢-+/)@(5↑⍠["fill":9]) 1 2 3 ; (⊢-+/)@(5↑⍠["fill":8]⌽) 1 2 3 ; (⊢-+/)@{7↑⍠["fill":20] 5↑⍠["fill":9] ω} 1 2 3] +[(-↢ +/)@(5↑⍠["fill":9]) 1 2 3 ; (-↢ +/)@(5↑⍠["fill":8]⌽) 1 2 3 ; (-↢ +/)@{7↑⍠["fill":20] 5↑⍠["fill":9] ω} 1 2 3] ⍝ => [¯23 ¯22 ¯21 ; ¯21 ¯20 ¯19 ; ¯63 ¯62 ¯61] @@ -418,7 +418,7 @@ a←["k":3 "n":2]ρι6ₓ ◇ k←ι["k":3] ◇ [+/⍠k a;ρι⍠"n" a] ⍝ [[ f←{i←"r"•index≢ω ◇ ω.[i]×2} ◇ e←•explain "[f∂1 2 3 ; f∂1 2 3]" ◇ n←{↑(ι₀)ρω}¨e.value ◇ [(1↓e.programs.event)≡,⊂"replayed" ; 3↑¨n ; ≢∪n] ⍝ [$t ["_r·" "_r·"] 2ₓ] ⍝ fresh-index-map — a map calls a dfn that selects and computes by `•index` once on all its cells, which share each name that it draws. Where the dfn's result holds a drawn name, the map calls it once for each cell, and each cell's result has a name of its own. A selection by an index named for an axis of the cells keeps the cells' order of axes -m←3 4ρι12 ◇ f←{i←•index≢ω ◇ +/ω.[i]×1+i} ◇ e←•explain "f⍤1 m" ◇ r←{•index 3}¨1 2 ◇ [n j k]←•index 2 3 4 ◇ y←(2 3 4ρι24).[n j k] ◇ [e.value ; ≢e.fallbacks.reason ; ≢ρ(0⊃r)+1⊃r ; (ρ{ω.[k]}⍤2⊢y)≡ρy] ⍝ [[20 60 100] 0ₓ 2ₓ $t] +m←3 4ρι12 ◇ f←{i←•index≢ω ◇ +/ω.[i]×1+i} ◇ e←•explain "f⍤1 m" ◇ r←{•index 3}¨1 2 ◇ [n j k]←•index 2 3 4 ◇ y←(2 3 4ρι24).[n j k] ◇ [e.value ; ≢e.fallbacks.reason ; ≢ρ(0⊃r)+1⊃r ; (ρ{ω.[k]}⍤2y)≡ρy] ⍝ [[20 60 100] 0ₓ 2ₓ $t] ⍝ program-record — `∂` of a dfn on a record of arrays runs from a program: each of the record's arrays is one of its inputs, and the gradient is a record with the same keys. The second call replays it p←["w":1 2 3 ; "b":4] ◇ f←{+/(⊼ω.w)+ω.b} ◇ e←•explain "[f∂p ; f∂p]" ◇ [(1↓e.programs.event)≡,⊂"replayed" ; e.value≡2ρ⊂["w":2 4 6 ; "b":3]] ⍝ $t $t @@ -534,7 +534,7 @@ f←{ω×ρ•index⁝3} ◇ (f∂2)≡f∂2 ⍝ $f v←1 2 3 ◇ f←+/×⍨ ◇ g←+/(2×)⁻ ◇ ef←•explain "[f∂v ; f∂v ; f∂v]" ◇ eg←•explain "[g∂v ; g∂v ; g∂v]" ◇ [(↑⌽ef.programs.event)≡"replayed" ; (↑⌽eg.programs.event)≡"replayed" ; ↑ef.value ; ↑eg.value] ⍝ [$t $t [2 4 6] [0.5 0.5 0.5]] ⍝ program-tacit-data — a train built afresh at each call, because it reads a name, runs from its structure's program. Each array that it binds as an argument is an input, as a free name of a dfn is, so a replay takes each call's new values -v←1 2 3 ◇ g←{w←α ◇ (+/w×⊢)∂ω} ◇ e←•explain "[4 5 6 g v ; 7 8 9 g v ; 1 1 1 g v]" ◇ [(↑⌽e.programs.event)≡"replayed" ; e.value] ⍝ [$t (4 5 6◇7 8 9◇1 1 1)] +v←1 2 3 ◇ g←{w←α ◇ +/w× ∂ω} ◇ e←•explain "[4 5 6 g v ; 7 8 9 g v ; 1 1 1 g v]" ◇ [(↑⌽e.programs.event)≡"replayed" ; e.value] ⍝ [$t (4 5 6◇7 8 9◇1 1 1)] ⍝ program-positions — `∂` of a dfn that selects at positions from its left argument runs from a program, which reads the positions again at each replay. A pick from each row, as `⌷⍤[0 1]` makes it, selects every row at once. No trace stops v←0.5×ι10 ◇ m←0.1×3 4ρι12 ◇ p←[3 5 3]ₓ ◇ q←[1 2 2]ₓ ◇ r←[0 3 1]ₓ ◇ s←[2 2 0]ₓ ◇ f←{+/(ω.[α])*2} ◇ g←{+/(α⌷⁰ ¹ω)*2} ◇ e←•explain "[p f∂ v ; q f∂ v ; r g∂ m ; s g∂ m]" ◇ e.value,(≢e.programs.event),≢e.stopped.reason ⍝ [[0 0 0 6 0 5 0 0 0 0] [0 1 4 0 0 0 0 0 0 0] [0 0 0 0◇0 0 0 1.4◇0 1.8 0 0] [0 0 0.4 0◇0 0 1.2 0◇1.6 0 0 0] 4ₓ 0ₓ] @@ -3719,7 +3719,7 @@ m←[1 2◇3 4] ◇ {⎕←(•storage ω).view ◇ +/ω}∂ ,m •float¨[{ω}∂3ₓ₁ ; {ω}∂3ₓ₂ ; {ω}∂3ₓ₄ ; {ω}∂3ₓ₈ ; {+/ω×ω}∂[2 3]ₓ₄] ⍝ [32 32 64 64 64]ₓ ⍝ — Under with a predicate differentiates through the selection -{+/(2×)@[0<⊢] ω}∂ ¯1 2 3 ⍝ 1 2 2 +{+/(2×)@[0<] ω}∂ ¯1 2 3 ⍝ 1 2 2 ⍝ — Under with positions differentiates through the selection {+/(2×)@[1 2] ω}∂ 5 5 5 ⍝ 1 2 2 @@ -4444,7 +4444,7 @@ a←¿[256 256 4]ρ0 ◇ j←(¿[130 130 2]ρ100)ₓ ◇ r←¿[16 64 64 4]ρ0 w←¿1000ρ0 ◇ t←⌊10×w ◇ ∧/[({+/,⌈/50↕ω}∂ w)≡{+/,⌈/1×50↕ω}∂ w;({+/,⌊/50↕ω}∂ t)≡{+/,⌊/1×50↕ω}∂ t;({+/,⌈/3↕ω}∂ t)≡{+/,⌈/1×3↕ω}∂ t] ⍝ $t ⍝ window-sum-gradient-slides — Under `∂`, a sum along the windows' axis gives each item the sum of the cotangents of the windows that read it, at both ends and along a matrix's first axis, as summing built windows does -x←1.0×ι10 ◇ m←[6 4]ρ1.0×ι24 ◇ c←[4 4]ρ1.0+ι16 ◇ [{+/(1+ι8)×+/3↕ω}∂ x ; ({+/,c×+/⍠1⊢3↕ω}∂ m)≡{+/,c×+/⍠1⊢1×3↕ω}∂ m ; ({+/(1+ι6)×+/5↕ω}∂ x)≡{+/(1+ι6)×+/1×5↕ω}∂ x] ⍝ [[1 3 6 9 12 15 18 21 15 8] $t $t] +x←1.0×ι10 ◇ m←[6 4]ρ1.0×ι24 ◇ c←[4 4]ρ1.0+ι16 ◇ [{+/(1+ι8)×+/3↕ω}∂ x ; ({+/,c×+/⍠1⁝3↕ω}∂ m)≡{+/,c×+/⍠1⁝1×3↕ω}∂ m ; ({+/(1+ι6)×+/5↕ω}∂ x)≡{+/(1+ι6)×+/1×5↕ω}∂ x] ⍝ [[1 3 6 9 12 15 18 21 15 8] $t $t] ⍝ — With an edge mode in an options record, every windowed axis is centred, and its edges read by that mode ["size":3 "edge":"wrap"]↕1 2 3 4 ⍝ [4 1 2◇1 2 3◇2 3 4◇3 4 1] @@ -5843,7 +5843,7 @@ key←997|(7×i×i)+3×i←ι256ₓ ◇ y←(1+2×16|⍋key)÷32 ◇ x←(4↑y) m←16 16ρx ◇ s←32•float m t←{v←4096×64•float,ω ◇ +/(1ₓ+ι≢v)×⌊ₓv} [t +/0.50.95;t +/0⌈1⌊0.5+1.25×m-0.5;t ⌈⌿m-0.5;t +/0.5ₛ [4608000 4272128 446464 4600800 252160 4608000 1048576 8830976 836000]ₓ [541400 ¯260160 0]ₓ diff --git a/tests/reference/regex.bpl b/tests/reference/regex.bpl index a7df9a96..7fb07167 100644 --- a/tests/reference/regex.bpl +++ b/tests/reference/regex.bpl @@ -30,7 +30,7 @@ m←"abc" •r "(z)" "ab:12:$ cd:3:$" ⍝ regex:each — A bound pattern composes and works with Each -f←≢∘("text"↣⊃)∘(•r↢"(?i)cat") +f←≢"text"⊃ •r↢"(?i)cat" f¨"Cat cat" "dog" ⍝ [2 0]ₓ ⍝ regex:badpattern — Compile errors are located DOMAIN errors diff --git a/tests/reference/static.bpl b/tests/reference/static.bpl index 03ab8442..06f51968 100644 --- a/tests/reference/static.bpl +++ b/tests/reference/static.bpl @@ -133,7 +133,7 @@ um←(300000ρι200ₓ)ₓ₁ ◇ jv←300000ρι200ₓ ◇ g←∊(•explain " [$t $t $t] ⍝ — `∂` of a dfn under static kinds runs from a program that holds the whole call, its value and its derivative together. The first call with given argument types and shapes builds the program by tracing the call, later calls replay it, and both give what `∂` of a train gives, which runs without a program. Each of the three calls runs from a program -f←{+/α+ω×ω} ◇ i←+/∘(⊣+⊢×⊢) ◇ e←•explain "[3 f∂ 1.5 2.5 ; 3 f∂ 1.5 2.5 ; 3ₓ f∂ [1 2]ₓ]" ◇ [e.value ; e.value≡[3 i∂ 1.5 2.5 ; 3 i∂ 1.5 2.5 ; 3ₓ i∂ [1 2]ₓ] ; ≢e.programs.event] +f←{+/α+ω×ω} ◇ i←+/+↢⊼ ◇ e←•explain "[3 f∂ 1.5 2.5 ; 3 f∂ 1.5 2.5 ; 3ₓ f∂ [1 2]ₓ]" ◇ [e.value ; e.value≡[3 i∂ 1.5 2.5 ; 3 i∂ 1.5 2.5 ; 3ₓ i∂ [1 2]ₓ] ; ≢e.programs.event] [[3 5 ; 3 5 ; 2 4] ; $t ; 3ₓ] ⍝ — A program's logical functions on Booleans run on the Boolean table's kernels, as the interpreter's do. The mask that `∨` and `~` give is the gradient of `ω×` it. Both calls run from the program diff --git a/tests/test_expressions.py b/tests/test_expressions.py index c13c6072..263e378c 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -21,6 +21,9 @@ def test_expression_parts(): assert fn(mean, [2., 4., 9.]).py == 5. assert fn(plus.reduce, divide, b.tally, b.space, [2., 4., 9.]).py == 5. assert fn(plus.reduce, divide, b.tally, [2., 4., 9.]).py == 0 + stage = b.bpl('∘') + matrix = b.Array(np.array([[2., 4.], [6., 8.]])) + teq(fn(plus.reduce, divide, b.tally, stage, b.rank, 1, matrix).py, [3., 7.]) table = b.Array(a=[1, 2, 3], b=[4, 5, 6]) selected = fn(table, b.dot, [[1, 0]]) assert selected.keys == (('b', 'a'),)