Skip to main content

ARCHIVED: aplnb has moved into bAsedPL, and this repository is archived. The %apl and %%apl magics are now in basedpl.notebooks, and the Dyalog session API is now basedpl.dyalog.

aplnb

aplnb brings 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 for the implementation. For the J language, see the sibling project jnb.

Installation

Install aplnb and its bAsedPL runtime:

pip install aplnb

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

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:

v = %apl 1 2 4
v
[1., 2., 4.]

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

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:

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:

from basedpl import Array, plus, tally
nested = %apl (1 2)(3 4 5)
nested
[Array([1., 2.]), Array([3., 4., 5.])]
%%apl
n←(1 2)(3 4 5)
+/¨n
3 12

The same Each/reduction pattern works with division:

%%apl
÷/¨n
0.5 3.75

In Python, chain the corresponding word functions:

plus.reduce.each(nested)
[3., 12.]
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:

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:

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:

%%apl
n←⍳10
0=|⌝⍨n
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

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

%%apl
+⌿0=|⌝⍨n
1x 2x 2x 3x 2x 4x 2x 4x 3x 4x

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

%%apl
⍸2=+⌿0=|⌝⍨n
2x 3x 5x 7x

Substitute ⍳50 to list the primes up to 50:

%%apl
⍸2=+⌿0=|⌝⍨⍳50
2x 3x 5x 7x 11x 13x 17x 19x 23x 29x 31x 37x 41x 43x 47x

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

%%apl
primes ← {⍸2=+⌿0=|⌝⍨⍵}⍳
primes 50
2x 3x 5x 7x 11x 13x 17x 19x 23x 29x 31x 37x 41x 43x 47x

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

%%apl
ℙ⍳15
2x 3x 5x 7x 11x 13x 17x 19x 23x 29x 31x 37x 41x 43x 47x

The fibonacci sequence:

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:

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

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

%%apl
sales['Paris';'Feb']
+/['month']sales
50
('London':60 ⋄ 'Paris':150)

Native Python arrays retain both kinds of metadata:

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.

adjustment = Array([3, 2, 1], axis_keys=(('Mar', 'Feb', 'Jan'),), axis_names=('month',))
(sales + adjustment).df
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .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

Unlabelled arrays keep their usual positional behaviour. See Axis keys 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:

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

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

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

A dyadic call exports JSON. For files, compose a parser with •nget, e.g. •csv •nget 'orders.csv'. See files, CSV and JSON for dialect and file options.

%%apl
record •json ''
{"name":"Ada","scores":[8,9,10]}

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:

import numpy as np
from basedpl import Session
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:

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:

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:

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:

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:

%%apl
•nc 'sales' 'primes'
'pr' •nl 3
2x 3x
(primes)

See names and help 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:

apl.close()

The apl magics

Hold left Alt/Option for bAsedPL’s glyph keyboard: 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. 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 APL font:

%%apl
m←3 3⍴⍳9
m×10
10 20 30
40 50 60
70 80 90

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

v = %apl 3×⍳4
v.np
array([ 3.,  6.,  9., 12.])
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:

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 ;:

%%apl
m×10;

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

%%apl
v←2×⍳5
⎕←v
2 4 6 8 10

Convert to NumPy to use its methods:

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:

%%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'
(AB-12) (CD-3)
1x 7x
12:AB 3:CD

Probability distributions

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

%%apl
normal←•normal 0 1
normal.cdf ¯1 0 1
normal.quantile 0.025 0.5 0.975
0.15865525394505725 0.5 0.8413447460549428
¯1.9599639845400545 0 1.9599639845400538

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

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:

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

See distributions for the 17 families, and regex 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:

from aplnb.dyalog import Apl
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 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 for glyphs, array rules and system functions.

Learning APL

To start learning APL, follow the 17 video series run by Jeremy Howard, and have a look at the study notes. These use Dyalog APL. Interpreter-specific features and user commands differ in bAsedPL.

Release files for aplnb 0.3.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aplnb 0.3.3
File Size Uploaded
aplnb-0.3.3.tar.gz 31.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aplnb 0.3.3
File Interpreter ABI Platform
aplnb-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 56.2 kB

Release files / aplnb-0.3.3.tar.gz

Download URL aplnb-0.3.3.tar.gz
Size 31.8 kB
Tags Source
SHA-256 checksum
How to use checksums
1d974d968a66584308bea3510abeeffd267bfcd130dbf84c6ba38e141458c86e
BLAKE2b-256 checksum
How to use checksums
9f7df4a99c72cf7d387c5105e36deb71a2ad0130b36e86b9b3eb70ba0931b94d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release files / aplnb-0.3.3-py3-none-any.whl

Download URL aplnb-0.3.3-py3-none-any.whl
Size 24.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4bccd77cae1764adc1dcf56335f5f8d407e1d766e6a112b353c3d8c8b4c2e44a
BLAKE2b-256 checksum
How to use checksums
c9a4bf1b295ac44bb25d0af7bc119d7f1e907268487cceac1eddd515258b0d18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release history Release notifications | RSS feed

This release

0.3.3 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page