Metadata-Version: 2.4
Name: aplnb
Version: 0.3.2
Summary: bAsedPL in notebooks, with apl magics for Jupyter and IPython
Author-email: Jeremy Howard <info@answer.ai>
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/answerdotai/aplnb
Project-URL: Documentation, https://answerdotai.github.io/aplnb
Keywords: nbdev,jupyter,notebook,python,apl,basedpl
Classifier: Natural Language :: English
Classifier: Intended Audience :: Developers
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastcore
Requires-Dist: basedpl>=0.1.9
Requires-Dist: ipython>=9.17.1
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: fastcdp>=0.0.17; extra == "dev"
Requires-Dist: numpy; extra == "dev"
Requires-Dist: pandas; extra == "dev"
Dynamic: license-file

# aplnb


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

`aplnb` brings [bAsedPL](https://github.com/AnswerDotAI/basedpl) to Jupyter and IPython. Use `%%apl` for APL session output and `%apl` for native bAsedPL values in Python. Both share a persistent workspace.

See [`core`](https://answerdotai.github.io/aplnb/core.html) for the implementation. For the J language, see the sibling project [jnb](https://github.com/AnswerDotAI/jnb).

## Installation

Install aplnb and its bAsedPL runtime:

``` sh
pip install aplnb
```

No separate APL installation is needed. To load the magics automatically in IPython and Jupyter, run:

``` sh
aplnb_install
```

Or run `%load_ext aplnb` in an individual notebook. The interpreter starts on the first use of a magic.

## Native arrays

The line magic `%apl` returns a native `basedpl.Array`. Its immutable data stays in bAsedPL, retaining nesting, exact numbers and empty-array prototypes. No NumPy conversion is needed:

``` python
v = %apl 1 2 4
v
```

    [1., 2., 4.]

Python operators use bAsedPL’s array semantics. Python determines precedence, so this multiplies before adding:

``` python
v * 2 + 1
```

    [3., 5., 9.]

Indexing is one-based, as in APL. A full `:` selects an axis. Iteration yields rows of a matrix rather than individual elements:

``` python
a = %apl 2 3⍴⍳6
a.shape, a[1, :], a[:, 2], list(a)
```

    ((2, 3), [1., 2., 3.], [2., 5.], [[1., 2., 3.], [4., 5., 6.]])

bAsedPL also exposes functions by their word names. `plus.reduce` is `+/`; adding `.each` applies it to each nested element:

``` python
from basedpl import Array, plus, tally
```

``` python
nested = %apl (1 2)(3 4 5)
nested
```

    [Array([1., 2.]), Array([3., 4., 5.])]

``` python
%%apl
n←(1 2)(3 4 5)
+/¨n
```

<pre class="aplnb_out sax2">3 12</pre>

The same Each/reduction pattern works with division:

``` python
%%apl
÷/¨n
```

<pre class="aplnb_out sax2">0.5 3.75</pre>

In Python, chain the corresponding word functions:

``` python
plus.reduce.each(nested)
```

    [3., 12.]

``` python
f = plus.reduce.each
f(nested)
```

    [3., 12.]

Python integers become exact values. Functions compose too: `plus.reduce / tally` builds the mean function `+/÷≢`. Its result is still a native array, even when scalar:

``` python
exact = Array([1, 2, 4])
mean_native = plus.reduce / tally
mean_native(exact)
```

    Array((7, 3))

The native representation uses Python numeric notation: `2` is exact and `2.` is approximate. `.apl` returns the APL display as a string:

``` python
v.apl, exact.apl
```

    ('1 2 4', '1x 2x 4x')

### Example algorithms

Let’s create a function to list primes.

A prime has exactly two positive divisors. `|⌝` forms an outer product of remainders; `⍨` supplies the same argument on both sides. Row i below marks multiples of i:

``` python
%%apl
n←⍳10
0=|⌝⍨n
```

<pre class="aplnb_out sax2">1x 1x 1x 1x 1x 1x 1x 1x 1x 1x
0x 1x 0x 1x 0x 1x 0x 1x 0x 1x
0x 0x 1x 0x 0x 1x 0x 0x 1x 0x
0x 0x 0x 1x 0x 0x 0x 1x 0x 0x
0x 0x 0x 0x 1x 0x 0x 0x 0x 1x
0x 0x 0x 0x 0x 1x 0x 0x 0x 0x
0x 0x 0x 0x 0x 0x 1x 0x 0x 0x
0x 0x 0x 0x 0x 0x 0x 1x 0x 0x
0x 0x 0x 0x 0x 0x 0x 0x 1x 0x
0x 0x 0x 0x 0x 0x 0x 0x 0x 1x</pre>

`+⌿` sums down the rows, counting each candidate’s divisors:

``` python
%%apl
+⌿0=|⌝⍨n
```

<pre class="aplnb_out sax2">1x 2x 2x 3x 2x 4x 2x 4x 3x 4x</pre>

`2=` marks primes. Where (`⍸`) returns their positions, which equal the candidates because `n←⍳10`:

``` python
%%apl
⍸2=+⌿0=|⌝⍨n
```

<pre class="aplnb_out sax2">2x 3x 5x 7x</pre>

Substitute `⍳50` to list the primes up to 50:

``` python
%%apl
⍸2=+⌿0=|⌝⍨⍳50
```

<pre class="aplnb_out sax2">2x 3x 5x 7x 11x 13x 17x 19x 23x 29x 31x 37x 41x 43x 47x</pre>

Finally, name it. The dfn takes the candidates as `⍵`; the trailing `⍳` generates them:

``` python
%%apl
primes ← {⍸2=+⌿0=|⌝⍨⍵}⍳
primes 50
```

<pre class="aplnb_out sax2">2x 3x 5x 7x 11x 13x 17x 19x 23x 29x 31x 37x 41x 43x 47x</pre>

The built-in prime glyph `ℙ` returns the nth prime, counting from one. Applied to `⍳15`, it produces the same list directly:

``` python
%%apl
ℙ⍳15
```

<pre class="aplnb_out sax2">2x 3x 5x 7x 11x 13x 17x 19x 23x 29x 31x 37x 41x 43x 47x</pre>

The fibonacci sequence:

``` python
fib = %apl {⍵,+/¯2↑⍵}⍣15⊢1 1
fib.np
```

    array([1.000e+00, 1.000e+00, 2.000e+00, 3.000e+00, 5.000e+00, 8.000e+00,
           1.300e+01, 2.100e+01, 3.400e+01, 5.500e+01, 8.900e+01, 1.440e+02,
           2.330e+02, 3.770e+02, 6.100e+02, 9.870e+02, 1.597e+03])

Explanation:

1.  `1 1`: Initial seed (first two Fibonacci numbers)
2.  `{⍵,+/¯2↑⍵}`: Function that appends the sum of the last two elements
3.  `⍣15`: Apply the function 15 times
4.  `⊢`: Identity function, passes the initial argument (1 1) to the iteration

## Labelled arrays

Arrays can carry **keys for positions** and **names for axes**, without becoming a separate table type. A keyed vector pairs each axis name with its position keys, in axis order. Here `axes:` labels the rows by city and the columns by month:

``` python
%%apl
axes←'city' 'month':('London' 'Paris' ⋄ 'Jan' 'Feb' 'Mar')
sales←axes:[10 20 30 ⋄ 40 50 60]
sales
```

<pre class="aplnb_out sax2">       Jan Feb Mar
London  10  20  30
 Paris  40  50  60</pre>

Brackets select positions by key. Function qualifiers select axes by name: summing over `month` leaves a total for each city, retaining its labels.

``` python
%%apl
sales['Paris';'Feb']
+/['month']sales
```

<pre class="aplnb_out sax2">50
(&#x27;London&#x27;:60 ⋄ &#x27;Paris&#x27;:150)</pre>

Native Python arrays retain both kinds of metadata:

``` python
sales = %apl sales
sales.axis_names, sales.axis_keys
```

    (('city', 'month'), (('London', 'Paris'), ('Jan', 'Feb', 'Mar')))

Arithmetic matches named axes and aligns their keys, rather than relying on order. This adjustment lists the months backwards but still adds 1 to January, 2 to February and 3 to March in each city. `.df` copies the result to pandas, preserving labels; install `basedpl[pandas]` to use it.

``` python
adjustment = Array([3, 2, 1], axis_keys=(('Mar', 'Feb', 'Jan'),), axis_names=('month',))
(sales + adjustment).df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

| month  | Jan  | Feb  | Mar  |
|--------|------|------|------|
| city   |      |      |      |
| London | 11.0 | 22.0 | 33.0 |
| Paris  | 41.0 | 52.0 | 63.0 |

</div>

Unlabelled arrays keep their usual positional behaviour. See [Axis keys](https://answerdotai.github.io/basedpl/keyed.html) for construction, updates and alignment rules.

## CSV and JSON

CSV headers become keys on a vector of columns. Numeric columns use compact storage. These two orders total 101:

``` python
%%apl
nl←•ucs 10
orders←•csv 'price,qty',nl,'10.5,2',nl,'20,4'
+/orders.price×orders.qty
```

<pre class="aplnb_out sax2">101</pre>

JSON objects use the same keyed arrays. Parse a record and select a field:

``` python
%%apl
record←•json '{"name":"Ada","scores":[8,9,10]}'
record.name
+/record.scores
```

<pre class="aplnb_out sax2">Ada
27x</pre>

A dyadic call exports JSON. For files, compose a parser with `•nget`, e.g. `•csv •nget 'orders.csv'`. See [files, CSV and JSON](https://answerdotai.github.io/basedpl/data.html) for dialect and file options.

``` python
%%apl
record •json ''
```

<pre class="aplnb_out sax2">{&quot;name&quot;:&quot;Ada&quot;,&quot;scores&quot;:[8,9,10]}</pre>

## Using bAsedPL from Python

The magics use `basedpl.Session`. Calling a session returns a native array or function and prints explicit APL output. `apl.eval(...)` returns a `Result` with the native `.value` and captured `.output` without printing. Both suppress implicit APL display. Use `.np` or `.py` when you need a converted result:

``` python
import numpy as np
from basedpl import Session
```

``` python
apl = Session()
apl('3 3⍴⍳9').np
```

    array([[1., 2., 3.],
           [4., 5., 6.],
           [7., 8., 9.]])

Module and session attributes expose the builtins. Glyph names (`add`, `dash`, `mul`, `div`) accept either valence; operation names (`plus`, `subtract`, `times`, `divide`) curry a dyadic call. Thus `add` with one argument conjugates, while `plus(2)` binds the right argument:

``` python
apl.add(2+3j).py, apl.plus(2)(3).py, apl.times(2)([1, 2, 3]).py
```

    ((2-3j), 5, array([2, 4, 6]))

Keyword arguments bind Python values in the workspace. Square brackets read an APL expression or assign a value:

``` python
apl(x=np.arange(1, 6))
apl['v'] = [3,1,4,1,5]
apl['{⍵[⍋⍵]}v'].np
```

    array([1, 1, 3, 4, 5])

`fn` makes a composable `Function` from an APL function expression. Pass one argument for a monadic call or two for a dyadic call. Python integers stay exact, so `.py` converts this mean to a `Fraction` rather than a float:

``` python
apl('mean←{⍝ Mean of a vector\n(+/⍵)÷≢⍵}')
mean = apl.fn('mean')
mean([1,2,4]).py
```

    Fraction(7, 3)

### Names and help

Type `mean?` for its comment help and `mean??` for APL source. The same information is available programmatically:

``` python
mean.source, apl.names('me')
```

    ('{⍝ Mean of a vector\n(+/⍵)÷≢⍵}', ['mean'])

In APL input, use `%apl ]help primes` or a `%%apl` cell containing `]help primes -source`. Tab completes user and system names. APL code can inspect names directly:

``` python
%%apl
•nc 'sales' 'primes'
'pr' •nl 3
```

<pre class="aplnb_out sax2">2x 3x
(primes)</pre>

See [names and help](https://answerdotai.github.io/basedpl/introspection.html) for source lookup, erasure and inspection APIs.

`fn` is late-bound: `apl.fn('foo')` follows later redefinitions of `foo`. Use `create_magic(session=apl)` to share a Python session with the magics.

Use a session as a context manager (`with Session() as apl:`), or close it when finished:

``` python
apl.close()
```

## The `apl` magics

Hold **left Alt/Option** for [bAsedPL’s glyph keyboard](https://answerdotai.github.io/basedpl/keyboard.html): Alt-h `←`, Alt-minus `×`, Alt-equals `÷`, Alt-Shift-a `⍶`. Right Option keeps its native behavior. Chords work in APL input, including strings and comments.

Type a backtick followed by a bAsedPL symbol name: `` `io `` then Tab inserts `⍳`, and `` 2`times3 `` becomes `2×3`. Suggestions appear beside the cursor as you type, including the REPL’s shortcut notation: `h` means Alt-h, `Sa` means Alt-Shift-a. Click a suggestion or keep typing to resolve an ambiguous name. Names and aliases come from bAsedPL’s REPL catalogue.

Completion is active in `%%apl` cells and on `%apl` lines, including `x = %apl ...`. Ordinary Python and Markdown input is unchanged. Strings, comments and pasted text are not expanded. Tab explicitly completes an existing name. Enter accepts a unique match before the notebook’s normal newline or execution action. Escape or cursor movement cancels automatic expansion.

The first `apl` magic also adds a clickable symbol bar, based on Adám Brudzewsky’s [APL language bar](https://abrudz.github.io/lb/apl). Hover over a glyph to see its names and shortcut. The `▲`/`▼` button switches between pushing the page down and overlaying it. This choice is remembered per site.

The cell magic (`%%apl`) displays bAsedPL session output in Adám’s [SAX2](https://github.com/abrudz/SAX2) APL font:

``` python
%%apl
m←3 3⍴⍳9
m×10
```

<pre class="aplnb_out sax2">10 20 30
40 50 60
70 80 90</pre>

Assignments are shy: the `m←` line printed nothing. The line magic returns a native array. Use `.np` to copy its values to NumPy:

``` python
v = %apl 3×⍳4
v.np
```

    array([ 3.,  6.,  9., 12.])

``` python
text = %apl 'APL in Python'
text.py
```

    'APL in Python'

`.py` converts numeric atoms to Python numbers and character vectors to strings. Unkeyed numeric arrays become NumPy arrays, keyed vectors become dictionaries, and higher-rank keyed arrays become pandas DataFrames. Numeric scalars (rank-0 arrays) become zero-dimensional NumPy arrays. `.np` copies values to NumPy without labels:

``` python
z = %apl m
z.np
```

    array([[1., 2., 3.],
           [4., 5., 6.],
           [7., 8., 9.]])

To suppress a cell’s output, end the last line with a `;`:

``` python
%%apl
m×10;
```

`⎕←` displays a value explicitly, which is how you show something that would otherwise be shy:

``` python
%%apl
v←2×⍳5
⎕←v
```

<pre class="aplnb_out sax2">2 4 6 8 10</pre>

Convert to NumPy to use its methods:

``` python
a = %apl m
a.np.sum(axis=0)
```

    array([12., 15., 18.])

## Regex

`•r` compiles a Rust regex into functions for matching, positions, groups and replacement:

``` python
%%apl
codes←•r '([A-Z]+)-([0-9]+)'
codes.match 'AB-12 CD-3'
codes.position 'AB-12 CD-3'
'$2:$1' codes.replace 'AB-12 CD-3'
```

<pre class="aplnb_out sax2">(AB-12) (CD-3)
1x 7x
12:AB 3:CD</pre>

## Probability distributions

Construct a standard normal, then evaluate its CDF and quantiles. Distribution methods accept arrays:

``` python
%%apl
normal←•normal 0 1
normal.cdf ¯1 0 1
normal.quantile 0.025 0.5 0.975
```

<pre class="aplnb_out sax2">0.15865525394505725 0.5 0.8413447460549428
¯1.9599639845400545 0 1.9599639845400538</pre>

Sampling takes a shape. These draws remain a native array until `.np` converts them:

``` python
draws = %apl normal.sample 2 3
draws.np
```

    array([[ 1.13425439,  0.50212334, -0.69961591],
           [ 2.16752983, -0.70488529, -1.72144746]])

For discrete distributions, `density` gives probability mass. A fair coin tossed twice has probabilities ¼, ½, ¼ for zero, one or two heads:

``` python
%%apl
coin←•binomial 2 0.5
coin.density 0 1 2
```

<pre class="aplnb_out sax2">0.25 0.5 0.25</pre>

See [distributions](https://answerdotai.github.io/basedpl/distributions.html) for the 17 families, and [regex](https://answerdotai.github.io/basedpl/regex.html) for captures and replacement options.

## Native APL kernel

The `basedpl` package also installs the **bAsedPL** Jupyter kernel. In that kernel, write APL directly without `%%apl`. Shift-Tab inspects names and glyphs; `]help name` and `]help name -source` show help and source. Use aplnb’s Python kernel integration when mixing Python and APL in the same notebook.

## Dyalog reference sessions

Use `aplnb.dyalog` when you need Dyalog as an independent reference interpreter. Dyalog must be installed separately. This session API does not change the `%apl` or `%%apl` magics, which continue to use bAsedPL:

``` python
from aplnb.dyalog import Apl
```

``` python
with Apl() as dyalog: total = dyalog.pyval('+/⍳10')
total
```

    55

`pyval` returns JSON-converted Python values. `run` returns session output as text. See [Dyalog sessions](https://answerdotai.github.io/aplnb/dyalog.html) for assignment, function calls and error handling.

## Errors and interruption

APL errors raise `basedpl.AplError`. The magics display output produced before the error. Incomplete input raises a syntax error without resetting the workspace.

Interrupt a calculation with the notebook’s stop button. Set a per-evaluation deadline with `Session(timeout=seconds)` or `magic.session.timeout = seconds`. Sessions use a Rust worker thread. Cooperative cancellation preserves the workspace and completed assignments. Native-library calls and individual BigInt operations can delay cancellation; the thread is never forcibly killed.

bAsedPL is an APL-derived array language, borrowing from J and BQN. See its [language guide](https://answerdotai.github.io/basedpl/) for glyphs, array rules and system functions.

## Learning APL

To start learning APL, follow the [17 video series](https://forums.fast.ai/t/apl-array-programming/97188) run by Jeremy Howard, and have a look at the [study notes](https://fastai.github.io/apl-study/apl.html). These use Dyalog APL. Interpreter-specific features and user commands differ in bAsedPL.
